artisan-core 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,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,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
|