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,15 @@
1
+ module Artisan
2
+ module Stories
3
+ class StoryNumberer
4
+ def initialize(project)
5
+ @project = project
6
+ end
7
+
8
+ def next_story_number
9
+ max = 0
10
+ Story.by_project(@project).each {|story| max = story.number if story.number && story.number > max}
11
+ return max + 1
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ module Artisan
2
+ module Stories
3
+ class StoryPresenter
4
+ attr_accessor :story
5
+
6
+ def initialize(story)
7
+ @story= story
8
+ end
9
+
10
+ def short_name
11
+ @story.name.length <= 30 ? @story.name : (@story.name[0..26] + "...")
12
+ end
13
+
14
+ def assigned_user_name
15
+ !@story.assigned_user.nil? ? @story.assigned_user.full_name : "Not Assigned"
16
+ end
17
+
18
+ def tags
19
+ @story.tags.map(&:name)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ require "artisan/stories/stories_interactor"
2
+
3
+ module Artisan
4
+ module Stories
5
+ class StorySorter
6
+
7
+ def initialize(project, state, story_ids, iteration)
8
+ @state = state
9
+ @story_ids = story_ids
10
+ @iteration = iteration
11
+ @project = project
12
+ end
13
+
14
+ def sort
15
+ get_stories.each do |story|
16
+ story.position = story_index(story) if has_story_id?(story)
17
+ Artisan::Repository.story.save(story)
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def has_story_id?(story)
24
+ return @story_ids.index(story.id.to_s).present?
25
+ end
26
+
27
+ def story_index(story)
28
+ return @story_ids.index(story.id.to_s) + 1
29
+ end
30
+
31
+ def get_stories
32
+ return Stories.for(@project.id, nil).backlog if @state == "backlog"
33
+ return @iteration.stories.send(@state)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,45 @@
1
+ require 'artisan/repository'
2
+
3
+ module Artisan
4
+ class StoryBoard
5
+ attr_reader :iteration
6
+
7
+ def initialize(iteration)
8
+ @iteration = iteration
9
+ end
10
+
11
+ def ready
12
+ repository.ready(iteration)
13
+ end
14
+
15
+ def working
16
+ repository.working(iteration)
17
+ end
18
+
19
+ def completed
20
+ repository.completed(iteration)
21
+ end
22
+
23
+ def backlog_total
24
+ Artisan::Stories::StoriesInteractor.new(iteration.project).backlog.points
25
+ end
26
+
27
+ def ready_total
28
+ ready.inject(0) { |sum, story| sum + story.estimate }
29
+ end
30
+
31
+ def working_total
32
+ working.inject(0) { |sum, story| sum + story.estimate }
33
+ end
34
+
35
+ def complete_total
36
+ completed.inject(0) { |sum, story| sum + story.estimate }
37
+ end
38
+
39
+ private
40
+
41
+ def repository
42
+ Artisan::Repository.story
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,71 @@
1
+ require "artisan/activity/story_auditor"
2
+ require "artisan/repository"
3
+
4
+ module Artisan
5
+ class StoryColumnChanger
6
+
7
+ def initialize(story, user)
8
+ @story = story
9
+ @user = user
10
+ end
11
+
12
+ def move_to_backlog
13
+ clear_completed
14
+ clear_working
15
+
16
+ story_repository.remove_from_iteration(@story)
17
+
18
+ story_repository.save(@story)
19
+ Artisan::Activity::StoryAuditor::backlogged(@story.id, @story.project.id, @user.id)
20
+ end
21
+
22
+ def move_to_ready(iteration)
23
+ clear_completed
24
+ clear_working
25
+
26
+ story_repository.add_to_iteration(iteration, @story)
27
+
28
+ story_repository.save(@story)
29
+ Artisan::Activity::StoryAuditor::readied(@story.id, @story.project.id, @user.id)
30
+ end
31
+
32
+ def move_to_working(user, iteration)
33
+ clear_completed
34
+
35
+ story_repository.add_to_iteration(iteration, @story)
36
+ @story.assigned_user ||= user
37
+
38
+ story_repository.save(@story)
39
+ Artisan::Activity::StoryAuditor::worked(@story.id, @story.project.id, @user.id)
40
+ end
41
+
42
+ def move_to_completed(user, iteration, options = {})
43
+ story_repository.add_to_iteration(iteration, @story)
44
+ @story.assigned_user ||= user
45
+
46
+ @story.complete = true
47
+ @story.completed_at = DateTime.now
48
+
49
+ story_repository.save(@story)
50
+
51
+ options[:event_mailer].completed_story(@story) if !options[:event_mailer].nil?
52
+ Artisan::Activity::StoryAuditor::completed(@story.id, @story.project.id, @user.id)
53
+ end
54
+
55
+ private
56
+
57
+
58
+ def clear_completed
59
+ @story.complete = false
60
+ @story.completed_at = nil
61
+ end
62
+
63
+ def clear_working
64
+ @story.assigned_user = nil
65
+ end
66
+
67
+ def story_repository
68
+ Artisan::Repository.story
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,14 @@
1
+ class StoryExporter
2
+
3
+ UNNECESSARY_FIELDS = [:complete, :completed_at, :creator_id, :deleted, :foreign_id, :iteration_id, :split_from, :updated_at]
4
+
5
+ def initialize(story)
6
+ @story = story
7
+ end
8
+
9
+ def to_json
10
+ return "{}" if @story.nil?
11
+ @story.to_json(:except => UNNECESSARY_FIELDS, :include => {:assigned_user => {:only => :full_name, :methods => [:gravatar_url]}})
12
+ end
13
+
14
+ end
@@ -0,0 +1,50 @@
1
+ require 'artisan/repository'
2
+ require 'ostruct'
3
+
4
+ class Team
5
+
6
+ attr_reader :project
7
+
8
+ def initialize(project, current_user)
9
+ @project = project
10
+ end
11
+
12
+ def add_team_member(user)
13
+ add_member(user, false)
14
+ end
15
+
16
+ def add_team_owner(user)
17
+ add_member(user, true)
18
+ end
19
+
20
+ def owner?(user)
21
+ return false if project.nil? || !user?(user)
22
+ project_repo.is_owner?(project, user)
23
+ end
24
+
25
+ def user?(user)
26
+ project_repo.is_member?(project, user)
27
+ end
28
+
29
+ def remove_user(user)
30
+ project_repo.remove_member(project, user)
31
+ if !user.nil?
32
+ Artisan::Member.new(OpenStruct.new(:user => user, :project => project)).remove_from_email_lists
33
+ end
34
+ end
35
+
36
+ def owners
37
+ project_repo.owners(project)
38
+ end
39
+
40
+ private
41
+
42
+ def add_member(user, owner)
43
+ project_repo.add_member(project, user, owner) unless user?(user)
44
+ end
45
+
46
+ def project_repo
47
+ Artisan::Repository.project
48
+ end
49
+
50
+ end
@@ -0,0 +1,100 @@
1
+ require "artisan/crud_strategy"
2
+ require "artisan-memory-repository/models/user"
3
+ require "artisan/callbacks"
4
+
5
+ class TestModel
6
+ attr_accessor :id, :attributes
7
+ def initialize
8
+ @id = 3
9
+ @attributes = {}
10
+ end
11
+ end
12
+
13
+ class TestRepository
14
+ def save(model)
15
+ true
16
+ end
17
+
18
+ def new(*args)
19
+ TestModel.new
20
+ end
21
+ end
22
+
23
+ describe Artisan::CrudStrategy do
24
+
25
+ let(:model) { TestModel.new }
26
+ let(:model_attributes) { {:test => "test"}}
27
+ let(:user) { Artisan::Repository.user.new }
28
+
29
+ let(:callback) do
30
+ Artisan::Callbacks.build do |callback|
31
+ callback.on(:success) { @success_callback_triggered = true }
32
+ callback.on(:failure) { @failure_callback_triggered = true }
33
+ end
34
+ end
35
+
36
+ let(:interactor) { Artisan::CrudStrategy }
37
+ let(:repository) { mock('repository') }
38
+
39
+ before do
40
+ TestModel.stub!(:find).and_return(model)
41
+ end
42
+
43
+ context "Create" do
44
+
45
+ before do
46
+ repository.stub!(:new).and_return(model)
47
+ repository.stub!(:save).and_return(true)
48
+ end
49
+
50
+ it "creates a new model" do
51
+ new_model = interactor.create(model, callback, repository)
52
+ new_model.should == model
53
+ end
54
+
55
+ it "calls successful callbacks" do
56
+ interactor.create(model_attributes, callback, repository)
57
+
58
+ @success_callback_triggered.should be_true
59
+ end
60
+
61
+ it "calls failure callbacks on failed save" do
62
+ failure_callback_triggered = false
63
+ failure_callback = Proc.new { |model| failure_callback_triggered = true }
64
+ repository.should_receive(:save).and_return(false)
65
+ interactor.create(model_attributes, callback, repository)
66
+
67
+ @failure_callback_triggered.should be_true
68
+ end
69
+
70
+ end
71
+
72
+
73
+ context "Update" do
74
+
75
+ before do
76
+ model.stub(:update_attributes).and_return(true)
77
+ end
78
+
79
+ it "finds and updates the project" do
80
+ model.should_receive(:update_attributes).with(model_attributes).and_return(true)
81
+
82
+ interactor.update(model, model_attributes, user, callback).should == model
83
+ end
84
+
85
+ it "completes a successful callback" do
86
+ interactor.update(model, model_attributes, user, callback)
87
+ @success_callback_triggered.should be_true
88
+ end
89
+
90
+ it "completes a failure callback" do
91
+ model.stub(:update_attributes).and_return(false)
92
+ failure_callback_triggered = false
93
+ failure_callback = Proc.new { failure_callback_triggered = true }
94
+ interactor.update(model, model_attributes, user, callback)
95
+ @failure_callback_triggered.should be_true
96
+ end
97
+
98
+ end
99
+
100
+ end
@@ -0,0 +1,86 @@
1
+ require "artisan/event_mailer"
2
+ require "artisan-memory-repository/models/project"
3
+ require "artisan-memory-repository/models/story"
4
+ require "artisan-memory-repository/models/user"
5
+
6
+ class Notifier; end # stubbed out for tests
7
+
8
+ describe EventMailer do
9
+
10
+ describe "mailing" do
11
+
12
+ before(:each) do
13
+ @project = Artisan::Repository.project.new(:name => "Test")
14
+ @project_configuration = double(:story_completed_email_list => ["hello@world.com", "dude@test.com"], :story_assigned_email_list => ["uhh@derp.com", "dude@test.com"])
15
+ @project.project_configuration = @project_configuration
16
+ @event_mailer = EventMailer.new(@project)
17
+ end
18
+
19
+ it "takes a project and sets a configuration" do
20
+ @event_mailer.instance_variable_get(:@configuration).should == @project_configuration
21
+ @event_mailer.instance_variable_get(:@project_name).should == @project.name
22
+ end
23
+
24
+ it "alerts the notifier for a new assigned user to a story" do
25
+ user = Artisan::Repository.user.new(:full_name => "Mister McGoo")
26
+ story = Artisan::Repository.story.new(:assigned_user => user, :name => "Test")
27
+
28
+ email_list = "uhh@derp.com, dude@test.com"
29
+ Notifier.should_receive(:new_assigned_user).with(@project.name, user.full_name, story.name, email_list).and_return(double(:deliver => nil))
30
+ @event_mailer.new_assigned_user(story)
31
+ end
32
+
33
+ it "alerts the notifier for a completed story on a project" do
34
+ story = Artisan::Repository.story.new(:name => "Test")
35
+
36
+ email_list = "hello@world.com, dude@test.com"
37
+ Notifier.should_receive(:completed_story).with(@project.name, story.name, email_list).and_return(double(:deliver => nil))
38
+ @event_mailer.completed_story(story)
39
+ end
40
+
41
+ end
42
+
43
+ context "archived projects" do
44
+ before(:each) do
45
+ @project = Artisan::Repository.project.new(:archived => true)
46
+ @event_mailer = EventMailer.new(@project)
47
+ end
48
+
49
+ it "does not alert the notifier of completed story if the project is archived" do
50
+ Notifier.should_not_receive(:completed_story)
51
+ @event_mailer.completed_story(nil)
52
+ end
53
+
54
+ it "does not alert the notifier of a new assigned user if the project is archived" do
55
+ Notifier.should_not_receive(:new_assigned_user)
56
+ @event_mailer.new_assigned_user(nil)
57
+ end
58
+
59
+ end
60
+
61
+ describe "empty email lists" do
62
+ before(:each) do
63
+ @project = Artisan::Repository.project.new(:name => "Test")
64
+ @project_configuration = double(:story_completed_email_list => [], :story_assigned_email_list => [])
65
+ @project.project_configuration = @project_configuration
66
+ @event_mailer = EventMailer.new(@project)
67
+ end
68
+
69
+ it "does not alert notififer if the assigned email list is empty" do
70
+ user = Artisan::Repository.user.new(:full_name => "Mister McGoo")
71
+ story = Artisan::Repository.story.new(:assigned_user => user, :name => "Test")
72
+
73
+ Notifier.should_not_receive(:new_assigned_user)
74
+ @event_mailer.new_assigned_user(story)
75
+ end
76
+
77
+ it "does not alert the notifier if the completed email list is empty" do
78
+ story = Artisan::Repository.story.new(:name => "Test")
79
+
80
+ Notifier.should_not_receive(:new_assigned_user)
81
+ @event_mailer.completed_story(story)
82
+ end
83
+
84
+ end
85
+
86
+ end