durt 0.11.0

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.
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'time_tracker'
4
+
5
+ module Durt
6
+ class NotifyTracker < TimeTracker
7
+ def self.enter(issue); end
8
+
9
+ def self.start(issue)
10
+ notify!("Started tracking: #{issue.label}")
11
+ end
12
+
13
+ def self.stop(issue)
14
+ tracked_time = issue.sessions.last.tracked_time
15
+ total = issue.total_tracked_time
16
+
17
+ notify!("Minutes tracked: #{(tracked_time / 60).ceil}")
18
+ notify!("Minutes tracked overall: #{(total / 60).ceil}")
19
+ end
20
+
21
+ def self.notify!(message)
22
+ system("notify-send '#{message}'")
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bug_tracker'
4
+
5
+ module Durt
6
+ class NullBugTracker < BugTracker
7
+ def active?
8
+ false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'time_tracker'
4
+
5
+ module Durt
6
+ class NullTimeTracker < TimeTracker
7
+ def self.active?
8
+ false
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bug_tracker'
4
+ require 'tracker_api'
5
+
6
+ module Durt
7
+ class PivotalBugTracker < BugTracker
8
+ attr_accessor :client
9
+
10
+ def after_initialize
11
+ @client = TrackerApi::Client.new(token: @config[:token])
12
+ end
13
+
14
+ def fetch_issues
15
+ fetch_stories
16
+ end
17
+
18
+ def fetch_stories
19
+ fetched_issues = source_project.stories(filter: "owner:#{current_user.id}")
20
+
21
+ fetched_issues.map do |issue|
22
+ {
23
+ key: issue.id,
24
+ summary: issue.name,
25
+ source: source_name,
26
+ project: project
27
+ }
28
+ end
29
+ end
30
+
31
+ # def comment(_key, _comment)
32
+ # nil
33
+ # end
34
+
35
+ private
36
+
37
+ def source_project
38
+ client.project(@config[:project])
39
+ end
40
+
41
+ def current_user
42
+ client.me
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'plugin'
4
+
5
+ module Durt
6
+ class PivotalPlugin < Plugin
7
+ def self.demo_config
8
+ { token: 'account_token', project: 'project' }
9
+ end
10
+
11
+ def bug_tracker_class
12
+ Durt::PivotalBugTracker
13
+ end
14
+
15
+ def config_required?
16
+ true
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-prompt'
4
+
5
+ module Durt
6
+ class Plugin
7
+ attr_reader :project, :config
8
+
9
+ PLUGINS = %w[Upwork Pivotal Jira Github Ebs Notify Local].freeze
10
+
11
+ def self.all
12
+ PLUGINS.map do |plugin_name|
13
+ klass = "Durt::#{plugin_name}Plugin"
14
+
15
+ klass.constantize
16
+ end
17
+ end
18
+
19
+ def initialize(project, config = nil)
20
+ @project = project
21
+ @config = config
22
+ end
23
+
24
+ def self.find_by_plugin_name(plugin_name)
25
+ all.find { |plugin| plugin.plugin_name == plugin_name.to_s }
26
+ end
27
+
28
+ def self.plugin_name
29
+ name.split('::').last.sub('Plugin', '')
30
+ end
31
+
32
+ def self.demo_config
33
+ nil
34
+ end
35
+
36
+ def filter(value)
37
+ value
38
+ end
39
+
40
+ def fetch_issues
41
+ bug_tracker.fetch_issues
42
+ end
43
+
44
+ def plugin_name
45
+ self.class.plugin_name
46
+ end
47
+
48
+ def switch_project
49
+ time_tracker.switch_project(project)
50
+ end
51
+
52
+ def before_enter(value)
53
+ value
54
+ end
55
+
56
+ def enter(issue)
57
+ issue
58
+ end
59
+
60
+ def start(issue)
61
+ issue
62
+ end
63
+
64
+ def stop(issue)
65
+ issue
66
+ end
67
+
68
+ def push_issue(issue)
69
+ issue.key
70
+ end
71
+
72
+ def source_name
73
+ bug_tracker.source_name
74
+ end
75
+
76
+ def issues
77
+ bug_tracker.issues
78
+ end
79
+
80
+ def time_tracker
81
+ time_tracker_class
82
+ end
83
+
84
+ def bug_tracker
85
+ if config_required? && not_configured?
86
+ raise NotConfiguredError, "#{plugin_name} plugin is not configured"
87
+ end
88
+
89
+ bug_tracker_class.new(project, @config)
90
+ end
91
+
92
+ private
93
+
94
+ def config_required?
95
+ false
96
+ end
97
+
98
+ def not_configured?
99
+ @config == self.class.demo_config
100
+ end
101
+
102
+ def time_tracker_class
103
+ Durt::NullTimeTracker
104
+ end
105
+
106
+ def bug_tracker_class
107
+ Durt::NullBugTracker
108
+ end
109
+
110
+ class NotConfiguredError < StandardError
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'application_record'
4
+
5
+ module Durt
6
+ class Project < ApplicationRecord
7
+ include Configurable
8
+
9
+ has_many :issues, dependent: :destroy
10
+
11
+ def self.current_project
12
+ @current_project ||= find_by!(active: true)
13
+ end
14
+
15
+ def to_s
16
+ name
17
+ end
18
+
19
+ def plugins
20
+ @plugins ||=
21
+ config['plugins'].map do |plugin_name, plugin_config|
22
+ Durt::Plugin.find_by_plugin_name(plugin_name).new(self, plugin_config)
23
+ end
24
+ end
25
+
26
+ def bug_tracker_plugins
27
+ plugins.find_all { |p| p.bug_tracker.active? }
28
+ end
29
+
30
+ def time_tracker_plugins
31
+ plugins.find_all { |p| p.time_tracker.active? }
32
+ end
33
+
34
+ def config_key
35
+ name
36
+ end
37
+
38
+ def puts_stats
39
+ issues.each(&:puts_stats)
40
+ end
41
+
42
+ def active_issue
43
+ issues.find_by!(active: true)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+ require_relative 'plugin'
5
+
6
+ module Durt
7
+ class ProjectController
8
+ attr_reader :project
9
+
10
+ def initialize(project)
11
+ @project = project
12
+ end
13
+
14
+ def current_issue
15
+ project.active_issue
16
+ end
17
+
18
+ def select_issue(issue = nil)
19
+ issue ||=
20
+ begin
21
+ plugin = select_source(project)
22
+
23
+ prompt.select("Select issue: ", plugin.issues.to_choice_h)
24
+ end
25
+
26
+ project.issues.update_all(active: false)
27
+ issue.tap(&:active!)
28
+ end
29
+
30
+ def filter
31
+ project.tap do |p|
32
+ p.plugins.each do |plugin|
33
+ plugin.filter(nil)
34
+ end
35
+ end
36
+ end
37
+
38
+ def start_issue(issue)
39
+ issue.tap do |i|
40
+ plugins = project.time_tracker_plugins
41
+
42
+ plugins.each do |plugin|
43
+ plugin.start(i)
44
+ end
45
+ end
46
+
47
+ issue.start_tracking!
48
+ end
49
+
50
+ def stop_issue(issue)
51
+ issue.tap do |i|
52
+ plugins = project.time_tracker_plugins
53
+
54
+ plugins.each do |plugin|
55
+ plugin.stop(i)
56
+ end
57
+ end
58
+
59
+ issue.stop_tracking!
60
+ end
61
+
62
+ def enter_issue(issue)
63
+ project.tap do |p|
64
+ plugins = p.plugins
65
+
66
+ plugins.each do |plugin|
67
+ plugin.before_enter(issue)
68
+ end
69
+
70
+ plugins.each do |plugin|
71
+ plugin.enter(issue)
72
+ end
73
+ end
74
+ end
75
+
76
+ def new_issue
77
+ plugin = select_source(project)
78
+
79
+ issue_name = prompt.ask('Enter issue name:')
80
+
81
+ issue_data = {
82
+ # Key should probably be ref_id and not be required
83
+ key: SecureRandom.uuid,
84
+ summary: issue_name,
85
+ project: project,
86
+ source: plugin.source_name
87
+ }
88
+
89
+ Durt::Issue.create(issue_data)
90
+ end
91
+
92
+ def push_issue(issue)
93
+ plugin = issue.plugin
94
+
95
+ ref_id = plugin.push_issue(issue)
96
+
97
+ issue.update(key: ref_id)
98
+ end
99
+
100
+ def sync_issues
101
+ project.tap do |p|
102
+ plugin = select_source(p, include_local: false)
103
+
104
+ fetched = plugin.fetch_issues
105
+
106
+ fetched.map do |attrs|
107
+ Durt::Issue.find_or_create_by(key: attrs[:key]) do |issue|
108
+ issue.attributes = attrs
109
+ end
110
+ end
111
+ end
112
+ end
113
+
114
+ private
115
+
116
+ def bug_tracker_choices_for(project, include_local = true)
117
+ choice_plugins = project.bug_tracker_plugins
118
+ choice_plugins = choice_plugins.reject { |p| p.plugin_name == 'Local' } unless include_local
119
+
120
+ choice_plugins.map { |p| [p.plugin_name, p] }.to_h
121
+ end
122
+
123
+ def select_source(project, include_local: true)
124
+ choices = bug_tracker_choices_for(project, include_local)
125
+
126
+ return choices.values.first if choices.count == 1
127
+
128
+ prompt.select('Select source', choices)
129
+ end
130
+
131
+ def prompt
132
+ @prompt ||= TTY::Prompt.new
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Durt
4
+ class Service
5
+ # Based on https://github.com/Selleo/pattern/blob/master/lib/patterns/service.rb
6
+
7
+ attr_reader :result
8
+ attr_reader :state
9
+
10
+ def self.call(*args)
11
+ new(*args).tap do |service|
12
+ service.instance_variable_set('@result', service.call)
13
+ end
14
+ end
15
+
16
+ def call
17
+ steps.each do |step|
18
+ @state = step.call(@state)
19
+ end
20
+
21
+ self
22
+ end
23
+
24
+ private
25
+
26
+ def steps
27
+ @steps ||= []
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'application_record'
4
+
5
+ module Durt
6
+ class Session < ApplicationRecord
7
+ belongs_to :issue
8
+
9
+ scope :tracking, -> { where(closed_at: nil) }
10
+
11
+ def tracked_time
12
+ (closed_at || Time.now) - open_at
13
+ end
14
+ end
15
+ end