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.
- data/lib/artisan/activity/activity_interactor.rb +18 -0
- data/lib/artisan/activity/activity_presenter.rb +35 -0
- data/lib/artisan/activity/formatters/diff_changes.rb +41 -0
- data/lib/artisan/activity/formatters/name.rb +22 -0
- data/lib/artisan/activity/formatters/source.rb +16 -0
- data/lib/artisan/activity/formatters/temporal.rb +20 -0
- data/lib/artisan/activity/iteration_auditor.rb +44 -0
- data/lib/artisan/activity/project_auditor.rb +28 -0
- data/lib/artisan/activity/story_auditor.rb +42 -0
- data/lib/artisan/callbacks.rb +27 -0
- data/lib/artisan/crud_strategy.rb +23 -0
- data/lib/artisan/event_mailer.rb +22 -0
- data/lib/artisan/inviter.rb +36 -0
- data/lib/artisan/iterations/iteration_point_calculator.rb +28 -0
- data/lib/artisan/iterations/iteration_presenter.rb +20 -0
- data/lib/artisan/iterations/iteration_workflow_interactor.rb +60 -0
- data/lib/artisan/iterations/iteration_workflow_presenter.rb +11 -0
- data/lib/artisan/iterations/iterations.rb +47 -0
- data/lib/artisan/iterations/iterations_interactor.rb +48 -0
- data/lib/artisan/iterations/move_to_backlog.rb +28 -0
- data/lib/artisan/iterations/story_tags.rb +15 -0
- data/lib/artisan/member.rb +52 -0
- data/lib/artisan/no_op_callbacks.rb +6 -0
- data/lib/artisan/projects/api_key_generator.rb +26 -0
- data/lib/artisan/projects/archive_interactor.rb +29 -0
- data/lib/artisan/projects/completed_stories_presenter.rb +20 -0
- data/lib/artisan/projects/iteration_numberer.rb +15 -0
- data/lib/artisan/projects/project_creator.rb +51 -0
- data/lib/artisan/projects/projects_interactor.rb +58 -0
- data/lib/artisan/projects/projects_presenter.rb +46 -0
- data/lib/artisan/projects/story_point_summer.rb +16 -0
- data/lib/artisan/reports/average_stat_per_iteration_data.rb +55 -0
- data/lib/artisan/reports/burn_up_chart.rb +46 -0
- data/lib/artisan/reports/high_charts_data_retriever.rb +77 -0
- data/lib/artisan/reports/high_charts_interactor.rb +31 -0
- data/lib/artisan/reports/percentage_of_commitments_met_data.rb +47 -0
- data/lib/artisan/reports/signoff/pdf.rb +161 -0
- data/lib/artisan/reports/signoff/report.rb +120 -0
- data/lib/artisan/reports/velocity_report.rb +43 -0
- data/lib/artisan/repository.rb +51 -0
- data/lib/artisan/stories/pert_calculator.rb +62 -0
- data/lib/artisan/stories/stories_interactor.rb +78 -0
- data/lib/artisan/stories/story_collection.rb +66 -0
- data/lib/artisan/stories/story_exporter.rb +45 -0
- data/lib/artisan/stories/story_numberer.rb +15 -0
- data/lib/artisan/stories/story_presenter.rb +23 -0
- data/lib/artisan/stories/story_sorter.rb +37 -0
- data/lib/artisan/story_board.rb +45 -0
- data/lib/artisan/story_column_changer.rb +71 -0
- data/lib/artisan/story_exporter.rb +14 -0
- data/lib/artisan/team.rb +50 -0
- data/spec/crud_strategy_spec.rb +100 -0
- data/spec/event_mailer_spec.rb +86 -0
- data/spec/inviter_spec.rb +58 -0
- data/spec/member_spec.rb +58 -0
- data/spec/repository_spec.rb +32 -0
- data/spec/story_board_spec.rb +36 -0
- data/spec/story_collection_spec.rb +61 -0
- data/spec/story_column_changer_spec.rb +222 -0
- data/spec/story_exporter_spec.rb +25 -0
- data/spec/story_sorter_spec.rb +67 -0
- data/spec/team_spec.rb +107 -0
- 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
|