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