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,120 @@
1
+ require "forwardable"
2
+ require "artisan/stories/story_presenter"
3
+ require "artisan/iterations/iteration_presenter"
4
+ require "artisan/iterations/story_tags"
5
+
6
+ module Signoff
7
+ class Report
8
+ extend Forwardable
9
+
10
+ attr_reader :project_name
11
+ attr_reader :table_data
12
+
13
+ def initialize(iteration, project_name, options = {})
14
+ @iteration = iteration
15
+ @project_name = project_name
16
+ @options = options
17
+ @table_data = {}
18
+ generate_reporting_variables
19
+ end
20
+
21
+ def_delegators :@iteration, :start_date, :finish_date, :committed_points, :committed_points_at_completion
22
+
23
+ def iteration_name
24
+ return "Iteration #{@iteration.number}"
25
+ end
26
+
27
+ def tables
28
+ tables = []
29
+ @options["sections"].each do |key, value|
30
+ tables << key if value == "1"
31
+ end
32
+ return tables
33
+ end
34
+
35
+ def total_billed_points
36
+ Artisan::Iterations::IterationPresenter.new(@iteration).total_billed_points
37
+ end
38
+
39
+ def team_member_names
40
+ Artisan::Iterations::IterationPresenter.new(@iteration).team_names
41
+ end
42
+
43
+ def completed
44
+ report_on(@iteration.stories.select { |story| story.complete? })
45
+ end
46
+
47
+ def features
48
+ report_on(@iteration.stories.select { |story| !story.nonbillable? })
49
+ end
50
+
51
+ def tasks
52
+ report_on(@iteration.stories.select { |story| story.nonbillable? })
53
+ end
54
+
55
+ def tag attr
56
+ report_on(@iteration.stories.select { |story| story.tag_list.include?(attr) })
57
+ end
58
+
59
+ def untagged
60
+ report_on(@iteration.stories.select { |story| story.tag_list.empty? })
61
+ end
62
+
63
+ def column_widths
64
+ widths = [60, 280]
65
+ widths << 100 if show_owner?
66
+ return widths
67
+ end
68
+
69
+ def column_titles
70
+ titles = ["Points", "Description"]
71
+ titles << "Owner" if show_owner?
72
+ return titles
73
+ end
74
+
75
+ private
76
+
77
+ def report_on(stories)
78
+ data = []
79
+ total_points = 0
80
+ stories.each do |story|
81
+ presenter = Artisan::Stories::StoryPresenter.new(story)
82
+ row = [story.estimate.to_s, story.name]
83
+ row << presenter.assigned_user_name if show_owner?
84
+
85
+ data << row
86
+ total_points += story.estimate
87
+ end
88
+
89
+ total_points = total_points.round(2)
90
+ final_row = [total_points.to_s, ""]
91
+ final_row << "" if show_owner?
92
+ data << final_row
93
+ end
94
+
95
+ def show_owner?
96
+ if !@options["show_owner"].nil?
97
+ return @options["show_owner"] == "1"
98
+ end
99
+ return false
100
+ end
101
+
102
+ def generate_reporting_variables
103
+ if !@options["sections"].nil?
104
+ @table_data['completed'] = self.completed if @options["sections"]["Completed"] == "1"
105
+
106
+ @table_data['features'] = self.features if @options["sections"]["Features"] == "1"
107
+
108
+ @table_data['tasks'] = self.tasks if @options["sections"]["Tasks"] == "1"
109
+
110
+ @table_data['untagged'] = self.untagged if @options["sections"]["Untagged"] == "1"
111
+ end
112
+
113
+ Artisan::Iterations::StoryTags.new(@iteration).tags.each { |tag| make_tag_callback(tag) }
114
+ end
115
+
116
+ def make_tag_callback(attr)
117
+ @table_data[attr.to_s.downcase] = self.tag(attr)
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,43 @@
1
+ require "artisan/iterations/iteration_point_calculator"
2
+ module Reports
3
+ class VelocityReport
4
+ attr_accessor :results, :project, :limit
5
+
6
+ def initialize(project)
7
+ @project = project
8
+ end
9
+
10
+ def generate(criteria = {})
11
+ @limit = !criteria[:limit].nil? ? criteria[:limit] : @project.iterations.count
12
+ @results = limited_iterations_by_finish_date
13
+ end
14
+
15
+ def collect_iteration(action)
16
+ return @results.collect {|iteration| iteration.send(action.to_sym) }.reverse
17
+ end
18
+
19
+ def max_value
20
+ list = array_to_f(collect_iteration(:committed_points)).concat(completed_points)
21
+ return list.sort[-1]
22
+ end
23
+
24
+ def collect_dates
25
+ return @results.collect{|iteration| iteration.finish_date.strftime('%b %d')}.reverse
26
+ end
27
+
28
+ def completed_points
29
+ return @results.collect {|iteration| Artisan::Iterations::IterationPointCalculator.new(iteration).completed_points }.reverse
30
+ end
31
+
32
+ private
33
+
34
+ def array_to_f(array)
35
+ return array.map(&:to_f)
36
+ end
37
+
38
+ def limited_iterations_by_finish_date
39
+ @project.iterations.by_finish_date[0, @limit.to_i]
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,51 @@
1
+ module Artisan
2
+ class RecordNotFound < RuntimeError
3
+ attr_reader :base
4
+
5
+ def initialize(base = nil)
6
+ @base = base
7
+ end
8
+ end
9
+
10
+ class Repository
11
+ def self.register_repo(repo)
12
+ @repo = repo
13
+ end
14
+
15
+ def self.story
16
+ repo.story
17
+ end
18
+
19
+ def self.project
20
+ repo.project
21
+ end
22
+
23
+ def self.iteration
24
+ repo.iteration
25
+ end
26
+
27
+ def self.user
28
+ repo.user
29
+ end
30
+
31
+ def self.future_user
32
+ repo.future_user
33
+ end
34
+
35
+ def self.project_configuration
36
+ repo.project_configuration
37
+ end
38
+
39
+ def self.change
40
+ repo.change
41
+ end
42
+
43
+ def self.member
44
+ repo.member
45
+ end
46
+
47
+ def self.repo
48
+ @repo
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,62 @@
1
+ class Float
2
+ def round_to(x)
3
+ (self/x).round * x
4
+ end
5
+ end
6
+
7
+ module Artisan
8
+ module Stories
9
+ class PertCalculator
10
+ attr_reader :story, :estimate_mode
11
+
12
+ def initialize(story, estimate_mode = "stddev")
13
+ @story = story
14
+ @estimate_mode = estimate_mode
15
+ end
16
+
17
+ def weighted_mean
18
+ unrounded_weighted_mean.round_to(0.25)
19
+ end
20
+
21
+ def standard_deviation
22
+ unrounded_standard_deviation.round(2)
23
+ end
24
+
25
+ def variance
26
+ unrounded_variance.round(2)
27
+ end
28
+
29
+ def estimate
30
+ mean = unrounded_weighted_mean
31
+ mean += unrounded_standard_deviation * 2 if @estimate_mode.to_s != "mean"
32
+ return mean.round_to(0.25)
33
+ end
34
+
35
+ private
36
+
37
+ def unrounded_weighted_mean
38
+ (optimistic.to_f + 4 * realistic.to_f + pessimistic.to_f) / 6
39
+ end
40
+
41
+ def unrounded_standard_deviation
42
+ (pessimistic.to_f - optimistic.to_f)/6
43
+ end
44
+
45
+ def unrounded_variance
46
+ unrounded_standard_deviation ** 2
47
+ end
48
+
49
+ def optimistic
50
+ @story.optimistic
51
+ end
52
+
53
+ def realistic
54
+ @story.realistic
55
+ end
56
+
57
+ def pessimistic
58
+ @story.pessimistic
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,78 @@
1
+ require "artisan/crud_strategy"
2
+ require 'artisan/activity/story_auditor'
3
+ require "artisan/stories/story_numberer"
4
+
5
+ module Artisan
6
+ module Stories
7
+
8
+ def self.for(project_id, user = nil)
9
+ StoriesInteractor.new(project_id, user)
10
+ end
11
+
12
+ class StoriesInteractor
13
+ def initialize(project_id, user = nil)
14
+ @project = project_repository.find(project_id)
15
+ @user = user
16
+ end
17
+
18
+ def create(story_attributes, callbacks)
19
+ model = repository.create_for_project(@project, story_attributes)
20
+ set_story_number_for model
21
+ story = CrudStrategy.create(model, callbacks, repository)
22
+ Activity::StoryAuditor.created(story.id, @project.id, @user.id, story_attributes)
23
+ return story
24
+ end
25
+
26
+ def update(number, story_attributes, callbacks)
27
+ story = find(number)
28
+ original_attributes = story.attributes
29
+ story = CrudStrategy.update(story, story_attributes, @user, callbacks)
30
+
31
+ Activity::StoryAuditor.updated(story.id, @project.id, @user.id, original_attributes, story_attributes)
32
+ end
33
+
34
+ def delete(number)
35
+ story = find(number)
36
+ repository.delete(story)
37
+ Activity::StoryAuditor.deleted(story.id, @project.id, @user.id)
38
+ end
39
+
40
+ def backlog(filter_tag = nil)
41
+ backlog_stories = filtered(filter_tag, @project.stories.backlog)
42
+ return StoryCollection.new(backlog_stories)
43
+ end
44
+
45
+ def completed
46
+ @project.stories.complete
47
+ end
48
+
49
+ def find_by_iteration(iteration_number)
50
+ iteration = @project.iterations.find_by_number iteration_number
51
+ iteration.stories(:include => 'assigned_user')
52
+ end
53
+
54
+ def find(number)
55
+ repository.find(number, @project)
56
+ end
57
+
58
+ private
59
+
60
+ def filtered(filter_tag, stories)
61
+ return stories if filter_tag.nil?
62
+ return stories.tagged_with(filter_tag, :on => :tags)
63
+ end
64
+
65
+ def set_story_number_for story
66
+ story.update_attribute(:number, StoryNumberer.new(@project).next_story_number)
67
+ end
68
+
69
+ def repository
70
+ Repository.story
71
+ end
72
+
73
+ def project_repository
74
+ Repository.project
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,66 @@
1
+ module Artisan
2
+ module Stories
3
+ class StoryCollection
4
+ include Enumerable
5
+
6
+ attr_reader :stories
7
+ alias :all :stories
8
+
9
+ def initialize(stories = [])
10
+ @stories = stories
11
+ end
12
+
13
+ def billable
14
+ select {|story| !story.nonbillable?}
15
+ end
16
+
17
+ def billed
18
+ select{|s| !s.nonbillable? && s.complete?}
19
+ end
20
+
21
+ def completed
22
+ select(&:complete?)
23
+ end
24
+
25
+ def incomplete
26
+ reject(&:complete?)
27
+ end
28
+
29
+ def billable_points
30
+ sum_estimates(billable)
31
+ end
32
+
33
+ def billed_points
34
+ sum_estimates(billed)
35
+ end
36
+
37
+ def estimated_points
38
+ sum_estimates(@stories)
39
+ end
40
+
41
+ alias :points :estimated_points
42
+ alias :total_points :estimated_points
43
+
44
+ def completed_points
45
+ sum_estimates(completed)
46
+ end
47
+
48
+ def all_complete?
49
+ all?(&:complete?)
50
+ end
51
+
52
+ def each(&block)
53
+ stories.each { |story| block.call(story) }
54
+ end
55
+
56
+ private
57
+
58
+ def sum_estimates(collection)
59
+ collection.inject(0.0) do |sum, story|
60
+ sum += story.estimate if story.estimate
61
+ sum
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,45 @@
1
+ module Artisan
2
+ module Stories
3
+ class StoryExporter
4
+
5
+ def initialize(story)
6
+ @story = story
7
+ end
8
+
9
+ def as_json(story_json = {}, options={})
10
+ options = {} unless options
11
+ options = neglect_created_at(options)
12
+
13
+ story_json = story_json
14
+
15
+ if @story.assigned_user
16
+ story_json["assigned_user"] = {
17
+ "full_name" => @story.assigned_user.full_name,
18
+ "gravatar_url" => @story.assigned_user.gravatar_url
19
+ }
20
+ end
21
+
22
+ story_json["assigned_user_options"] = @story.project.users.inject({}) do |collection, user|
23
+ collection[user.full_name] = user.id
24
+ collection
25
+ end if @story.project
26
+
27
+ story_json["created_at"] = @story.created_at.strftime("%m/%d/%Y") if !@story.created_at.nil?
28
+ story_json["creator"] = @story.creator.full_name if @story.creator
29
+ story_json["tag_list"] = @story.tag_list.join(",")
30
+ story_json
31
+ end
32
+
33
+ private
34
+
35
+ def neglect_created_at(options)
36
+ if options[:except]
37
+ options[:except] += [:created_at]
38
+ else
39
+ options[:except] = [:created_at]
40
+ end
41
+ options
42
+ end
43
+ end
44
+ end
45
+ end