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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +99 -0
- data/bin/durt +47 -0
- data/db/config.yml +22 -0
- data/db/sample.sqlite3 +0 -0
- data/lib/durt.rb +55 -0
- data/lib/durt/application_record.rb +13 -0
- data/lib/durt/bug_tracker.rb +42 -0
- data/lib/durt/command.rb +145 -0
- data/lib/durt/configurable.rb +34 -0
- data/lib/durt/ebs_plugin.rb +68 -0
- data/lib/durt/github_bug_tracker.rb +40 -0
- data/lib/durt/github_plugin.rb +19 -0
- data/lib/durt/global_controller.rb +55 -0
- data/lib/durt/issue.rb +94 -0
- data/lib/durt/jira_bug_tracker.rb +59 -0
- data/lib/durt/jira_plugin.rb +39 -0
- data/lib/durt/local_bug_tracker.rb +7 -0
- data/lib/durt/local_plugin.rb +11 -0
- data/lib/durt/notify_plugin.rb +19 -0
- data/lib/durt/notify_tracker.rb +25 -0
- data/lib/durt/null_bug_tracker.rb +11 -0
- data/lib/durt/null_time_tracker.rb +11 -0
- data/lib/durt/pivotal_bug_tracker.rb +45 -0
- data/lib/durt/pivotal_plugin.rb +19 -0
- data/lib/durt/plugin.rb +113 -0
- data/lib/durt/project.rb +46 -0
- data/lib/durt/project_controller.rb +135 -0
- data/lib/durt/service.rb +30 -0
- data/lib/durt/session.rb +15 -0
- data/lib/durt/status.rb +11 -0
- data/lib/durt/time_tracker.rb +23 -0
- data/lib/durt/upwork_plugin.rb +23 -0
- data/lib/durt/upwork_tracker.rb +88 -0
- data/lib/durt/version.rb +5 -0
- data/spec/durt/version_spec.rb +7 -0
- data/spec/spec_helper.rb +9 -0
- metadata +264 -0
@@ -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,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
|
data/lib/durt/plugin.rb
ADDED
@@ -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
|
data/lib/durt/project.rb
ADDED
@@ -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
|
data/lib/durt/service.rb
ADDED
@@ -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
|
data/lib/durt/session.rb
ADDED
@@ -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
|