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,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