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,18 @@
1
+ require "artisan/activity/activity_presenter"
2
+
3
+ module Artisan
4
+ module Activity
5
+ class ActivityInteractor
6
+ def initialize(project_id, user)
7
+ @project_id = project_id
8
+ @user = user
9
+ end
10
+
11
+ def changes
12
+ project_changes = ::Change.find_all_by_project_id(@project_id)
13
+ project = Projects::ProjectsInteractor.new(@user).find(@project_id)
14
+ return ActivityPresenter.new(project_changes, project)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ require 'artisan/activity/formatters/temporal'
2
+ require 'artisan/activity/formatters/name'
3
+ require 'artisan/activity/formatters/source'
4
+ require 'artisan/activity/formatters/diff_changes'
5
+
6
+ module Artisan
7
+ module Activity
8
+ class ActivityPresenter
9
+ attr_reader :project
10
+
11
+ def initialize(changes, project)
12
+ @changes = changes
13
+ @project = project
14
+ end
15
+
16
+ def changes
17
+ changes = []
18
+ @changes.each do |change|
19
+ temporal = Formatters::Temporal.new(change)
20
+
21
+ changes << {
22
+ :date => temporal.date,
23
+ :time => temporal.time,
24
+ :who => Formatters::Name.new(change).full_name,
25
+ :what => Formatters::Source.new(change).type,
26
+ :diffs => Formatters::DiffChanges.new(change).diffs,
27
+ :event => change.event
28
+ }
29
+ end
30
+ return changes
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,41 @@
1
+ module Artisan
2
+ module Activity
3
+ module Formatters
4
+ class DiffChanges
5
+
6
+ def initialize(change)
7
+ @change = change
8
+ end
9
+
10
+ def diffs
11
+ diffs = []
12
+ return diffs if @change.changed_fields.nil? || @change.original_fields.nil?
13
+
14
+ changed_fields = YAML::load(@change.changed_fields)
15
+ original_fields = YAML::load(@change.original_fields)
16
+
17
+ changed_fields.keys.each do |field|
18
+ if fields_different?(changed_fields, original_fields, field)
19
+ diffs << "#{field} changed from '#{display_nil(original_fields[field])}' to '#{display_nil(changed_fields[field])}'"
20
+ end
21
+ end
22
+ return diffs
23
+ end
24
+
25
+ private
26
+
27
+ def display_nil(value)
28
+ return "no value" if value.nil?
29
+ return value
30
+ end
31
+
32
+ def fields_different?(changed_fields, original_fields, field)
33
+ return false if field == "updated_at"
34
+ return false if changed_fields[field] == 'f' && original_fields[field].nil?
35
+ return false if changed_fields[field].nil? && original_fields[field] == 'f'
36
+ return changed_fields[field] != original_fields[field]
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ module Artisan
2
+ module Activity
3
+ module Formatters
4
+
5
+ class Name
6
+
7
+ def initialize(change)
8
+ @change = change
9
+ end
10
+
11
+ def full_name
12
+ unless @change.user_id == nil
13
+ return User.find(@change.user_id).full_name
14
+ else
15
+ return "Unknown"
16
+ end
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,16 @@
1
+ module Artisan
2
+ module Activity
3
+ module Formatters
4
+ class Source
5
+
6
+ def initialize(change)
7
+ @change = change
8
+ end
9
+
10
+ def type
11
+ return @change.type.gsub(/Change/, '')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ module Artisan
2
+ module Activity
3
+ module Formatters
4
+ class Temporal
5
+
6
+ def initialize(change)
7
+ @change = change
8
+ end
9
+
10
+ def date
11
+ return @change.created_at.strftime("%m/%d/%Y")
12
+ end
13
+
14
+ def time
15
+ return @change.created_at.strftime("%I:%M:%S %p")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,44 @@
1
+ module Artisan
2
+ module Activity
3
+ class IterationAuditor
4
+
5
+ def self.created(iteration_id, project_id, user_id, new_attributes)
6
+ create_change(:source_id => iteration_id, :project_id => project_id, :user_id => user_id, :event => :create,
7
+ :changed_fields => new_attributes)
8
+ end
9
+
10
+ def self.updated(iteration_id, project_id, user_id, original_attributes, new_attributes)
11
+ create_change(:source_id => iteration_id, :project_id => project_id, :user_id => user_id, :event => :update,
12
+ :original_fields => original_attributes, :changed_fields => new_attributes)
13
+ end
14
+
15
+ def self.deleted(iteration_id, project_id, user_id)
16
+ create_change(:source_id => iteration_id, :project_id => project_id, :user_id => user_id, :event => :delete,
17
+ :original_fields => {}, :changed_fields => {})
18
+ end
19
+
20
+ def self.completed(iteration_id, project_id, user_id)
21
+ create_change(:source_id => iteration_id, :project_id => project_id, :user_id => user_id, :event => :complete,
22
+ :original_fields => {}, :changed_fields => {})
23
+ end
24
+
25
+ def self.resumed(iteration_id, project_id, user_id)
26
+ create_change(:source_id => iteration_id, :project_id => project_id, :user_id => user_id, :event => :resume,
27
+ :original_fields => {}, :changed_fields => {})
28
+ end
29
+
30
+ def self.started(iteration_id, project_id, user_id)
31
+ create_change(:source_id => iteration_id, :project_id => project_id, :user_id => user_id, :event => :start,
32
+ :original_fields => {}, :changed_fields => {})
33
+ end
34
+
35
+
36
+ def self.create_change(attributes)
37
+ Repository.change.create_iteration_change(attributes)
38
+ end
39
+
40
+
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ module Artisan
2
+ module Activity
3
+ class ProjectAuditor
4
+
5
+ def self.created( project_id, user_id, new_attributes)
6
+ create_change(:source_id => project_id, :project_id => project_id, :user_id => user_id, :event => :create,
7
+ :changed_fields => new_attributes)
8
+ end
9
+
10
+ def self.updated(project_id, user_id, original_attributes, new_attributes)
11
+ create_change(:source_id => project_id, :project_id => project_id, :user_id => user_id, :event => :update,
12
+ :changed_fields => new_attributes, :original_fields => original_attributes)
13
+ end
14
+
15
+ def self.deleted(project_id, user_id)
16
+ create_change(:source_id => project_id, :project_id => project_id, :user_id => user_id, :event => :delete,
17
+ :changed_fields => {}, :original_fields => {})
18
+ end
19
+
20
+
21
+ def self.create_change(attrs)
22
+ Repository.change.create_project_change(attrs)
23
+ end
24
+
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,42 @@
1
+ module Artisan
2
+ module Activity
3
+ class StoryAuditor
4
+
5
+ def self.created(story_id, project_id, user_id, new_attributes)
6
+ story_change(story_id, project_id, user_id, :create, {}, new_attributes)
7
+ end
8
+
9
+ def self.updated(story_id, project_id, user_id, original_attributes, new_attributes)
10
+ story_change(story_id, project_id, user_id, :update, original_attributes, new_attributes)
11
+ end
12
+
13
+ def self.deleted(story_id, project_id, user_id)
14
+ story_change(story_id, project_id, user_id, :delete)
15
+ end
16
+
17
+ def self.backlogged(story_id, project_id, user_id)
18
+ story_change(story_id, project_id, user_id, :move_to_backlog)
19
+ end
20
+
21
+ def self.readied(story_id, project_id, user_id)
22
+ story_change(story_id, project_id, user_id, :move_to_ready)
23
+ end
24
+
25
+ def self.worked(story_id, project_id, user_id)
26
+ story_change(story_id, project_id, user_id, :move_to_working)
27
+ end
28
+
29
+ def self.completed(story_id, project_id, user_id)
30
+ story_change(story_id, project_id, user_id, :move_to_complete)
31
+ end
32
+
33
+ private
34
+
35
+ def self.story_change(story_id, project_id, user_id, event, original_fields = {}, changed_fields = {})
36
+ Repository.change.create_story_change(:source_id => story_id, :project_id => project_id, :user_id => user_id, :event => event,
37
+ :original_fields => original_fields, :changed_fields => changed_fields)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ module Artisan
2
+ class Callbacks
3
+ attr_reader :callbacks
4
+
5
+ def initialize
6
+ @callbacks = Hash.new
7
+ end
8
+
9
+ def self.build(&block)
10
+ builder = Callbacks.new
11
+ block.call(builder)
12
+ builder
13
+ end
14
+
15
+ def on(name, &block)
16
+ @callbacks[name] = Proc.new(&block)
17
+ end
18
+
19
+ def call(name, *args)
20
+ unless @callbacks.has_key?(name)
21
+ raise ArgumentError, "You are missing one of the expected callbacks. Missing: #{name} callback"
22
+ end
23
+
24
+ @callbacks[name].call(*args)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Artisan
2
+ class CrudStrategy
3
+
4
+ def self.create(model, callbacks, repository)
5
+ if repository.save(model)
6
+ callbacks.call(:success, model)
7
+ else
8
+ callbacks.call(:failure, model)
9
+ end
10
+ return model
11
+ end
12
+
13
+ def self.update(model, model_attributes, user, callbacks)
14
+ if model.update_attributes(model_attributes)
15
+ callbacks.call(:success, model)
16
+ else
17
+ callbacks.call(:failure, model)
18
+ end
19
+ return model
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ class EventMailer
2
+
3
+ def initialize(project)
4
+ @project_name = project.name
5
+ @configuration = project.project_configuration
6
+ @archived = project.archived
7
+ end
8
+
9
+ def new_assigned_user(story)
10
+ if !@archived
11
+ email_list = @configuration.story_assigned_email_list.join(", ")
12
+ Notifier.new_assigned_user(@project_name, story.assigned_user.full_name, story.name, email_list).deliver if !email_list.empty?
13
+ end
14
+ end
15
+
16
+ def completed_story(story)
17
+ if !@archived
18
+ email_list = @configuration.story_completed_email_list.join(", ")
19
+ Notifier.completed_story(@project_name, story.name, email_list).deliver if !email_list.empty?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ require 'artisan/repository'
2
+
3
+ class Inviter
4
+ EMAIL_REGEX = /^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.]*[a-zA-Z]$/
5
+
6
+ def initialize(name, email)
7
+ @name = name
8
+ @email = email
9
+ end
10
+
11
+ def invite
12
+ if valid_name? && valid_email?
13
+ mail_invite
14
+ else
15
+ raise "Name and email fields are required, please fill out all fields and try again."
16
+ end
17
+ end
18
+
19
+ def add_to_future_users(project)
20
+ Artisan::Repository.future_user.create(:email => @email, :project_id => project.id)
21
+ end
22
+
23
+ private
24
+
25
+ def valid_name?
26
+ return !@name.nil? && !@name.empty?
27
+ end
28
+
29
+ def valid_email?
30
+ return @email =~ EMAIL_REGEX
31
+ end
32
+
33
+ def mail_invite
34
+ Invites.invite(@name, @email).deliver
35
+ end
36
+ end
@@ -0,0 +1,28 @@
1
+ require "artisan/repository"
2
+ require "artisan/stories/pert_calculator"
3
+ module Artisan
4
+ module Iterations
5
+ class IterationPointCalculator
6
+
7
+ def initialize(iteration)
8
+ @iteration = iteration
9
+ end
10
+
11
+ def total_points
12
+ return total_of Repository.story.all(@iteration)
13
+ end
14
+
15
+ def completed_points
16
+ return total_of Repository.story.completed(@iteration)
17
+ end
18
+
19
+ private
20
+
21
+ def total_of(stories)
22
+ total = stories.inject(0){|total, story| total + story.estimate }
23
+ return total.round(2)
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module Artisan
2
+ module Iterations
3
+ class IterationPresenter
4
+
5
+ def initialize(iteration)
6
+ @iteration = iteration
7
+ end
8
+
9
+ def team_names
10
+ @iteration.users.collect { |user| user.full_name }.join(", ")
11
+ end
12
+
13
+ def total_billed_points
14
+ total = 0
15
+ @iteration.stories.completed.each { |story| total += story.estimate if !story.nonbillable?}
16
+ return total
17
+ end
18
+ end
19
+ end
20
+ end