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,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml/store'
|
4
|
+
|
5
|
+
module Durt
|
6
|
+
module Configurable
|
7
|
+
STORE_FILE_NAME = '.durt.yml'
|
8
|
+
STORE_FILE_PATH = File.expand_path("~/#{STORE_FILE_NAME}")
|
9
|
+
|
10
|
+
def config
|
11
|
+
config_store.transaction do
|
12
|
+
config_store[config_key]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def config!(value)
|
17
|
+
config_store.transaction do
|
18
|
+
config_store[config_key] = value
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def config?
|
23
|
+
!config.nil?
|
24
|
+
end
|
25
|
+
|
26
|
+
def config_key
|
27
|
+
raise NotImplementedError
|
28
|
+
end
|
29
|
+
|
30
|
+
def config_store
|
31
|
+
@config_store ||= YAML::Store.new(STORE_FILE_PATH)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'plugin'
|
4
|
+
|
5
|
+
module Durt
|
6
|
+
class EbsPlugin < Plugin
|
7
|
+
def before_enter(issue)
|
8
|
+
return if issue.estimate?
|
9
|
+
|
10
|
+
edit_estimate(issue)
|
11
|
+
end
|
12
|
+
|
13
|
+
def edit_estimate(issue)
|
14
|
+
puts issue.to_s
|
15
|
+
estimate_input =
|
16
|
+
prompt.ask('How long do you think this task will take you?')
|
17
|
+
|
18
|
+
input_in_seconds = estimate_input_to_seconds(estimate_input)
|
19
|
+
|
20
|
+
issue.update(estimate: input_in_seconds)
|
21
|
+
issue
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def estimate_input_to_seconds(input)
|
27
|
+
digit = input.gsub(/[^\d\.]/, '').to_f
|
28
|
+
measure_char = input.gsub(/[\d\.]/, '').strip.chr
|
29
|
+
|
30
|
+
time_in_seconds = if measure_char == 's'
|
31
|
+
digit
|
32
|
+
elsif measure_char == 'm'
|
33
|
+
digit * 60
|
34
|
+
elsif measure_char == 'h'
|
35
|
+
digit * 3600
|
36
|
+
else
|
37
|
+
raise WhatKindOfTimeIsThatError
|
38
|
+
end
|
39
|
+
|
40
|
+
time_in_seconds.ceil(2)
|
41
|
+
end
|
42
|
+
|
43
|
+
def prompt
|
44
|
+
@prompt ||= TTY::Prompt.new
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
module Durt
|
50
|
+
module Command
|
51
|
+
class EditEstimate < Durt::Service
|
52
|
+
def initialize
|
53
|
+
controller = Durt::ProjectController.new
|
54
|
+
|
55
|
+
steps << ->(_state) { controller.current_issue }
|
56
|
+
steps << ->(issue) { controller.edit_estimate(issue) }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Durt
|
63
|
+
class ProjectController
|
64
|
+
def edit_estimate(issue)
|
65
|
+
EbsPlugin.new(issue.project).edit_estimate(issue)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'bug_tracker'
|
4
|
+
require 'octokit'
|
5
|
+
|
6
|
+
module Durt
|
7
|
+
class GithubBugTracker < BugTracker
|
8
|
+
attr_accessor :client
|
9
|
+
|
10
|
+
def after_initialize
|
11
|
+
@client = Octokit::Client.new(@config)
|
12
|
+
@client.auto_paginate = true
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_issues
|
16
|
+
fetched_issues = client.issues(fetch_issues_query)
|
17
|
+
|
18
|
+
fetched_issues.map do |issue|
|
19
|
+
{
|
20
|
+
key: issue.number,
|
21
|
+
summary: issue.title,
|
22
|
+
source: source_name,
|
23
|
+
project: project
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# def comment(_key, _content)
|
29
|
+
# nil
|
30
|
+
# end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def fetch_issues_query
|
35
|
+
query = @config[:repo]
|
36
|
+
puts query
|
37
|
+
query
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'plugin'
|
4
|
+
|
5
|
+
module Durt
|
6
|
+
class GithubPlugin < Plugin
|
7
|
+
def self.demo_config
|
8
|
+
{ access_token: '<your 40 char token>', repo: 'rails/rails' }
|
9
|
+
end
|
10
|
+
|
11
|
+
def bug_tracker_class
|
12
|
+
Durt::GithubBugTracker
|
13
|
+
end
|
14
|
+
|
15
|
+
def config_required?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Durt
|
2
|
+
class GlobalController
|
3
|
+
def create_project
|
4
|
+
project_name = prompt.ask('What will you name your project?')
|
5
|
+
|
6
|
+
Durt::Project.create(name: project_name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_project_config(project)
|
10
|
+
project.tap { |p| p.config!('plugins' => plugins_config) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def select_project(project = nil)
|
14
|
+
projects = Durt::Project.all
|
15
|
+
|
16
|
+
project ||=
|
17
|
+
begin
|
18
|
+
prompt.select('Select project:', projects.to_choice_h)
|
19
|
+
end
|
20
|
+
|
21
|
+
projects.update_all(active: false)
|
22
|
+
project.tap(&:active!)
|
23
|
+
end
|
24
|
+
|
25
|
+
def switch_to_project(project)
|
26
|
+
project.tap do |p|
|
27
|
+
p.time_tracker_plugins.each(&:switch_project)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def console
|
32
|
+
binding.pry
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def plugins_config
|
38
|
+
all_plugins = Durt::Plugin.all
|
39
|
+
plugin_choices =
|
40
|
+
(all_plugins - [Durt::LocalPlugin])
|
41
|
+
.map { |p| [p.plugin_name, p] }
|
42
|
+
.to_h
|
43
|
+
|
44
|
+
prompt
|
45
|
+
.multi_select('Select plugins', plugin_choices)
|
46
|
+
.push(Durt::LocalPlugin)
|
47
|
+
.map { |p| [p.plugin_name, p.demo_config] }
|
48
|
+
.to_h
|
49
|
+
end
|
50
|
+
|
51
|
+
def prompt
|
52
|
+
@prompt ||= TTY::Prompt.new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
data/lib/durt/issue.rb
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'application_record'
|
4
|
+
require 'chronic_duration'
|
5
|
+
|
6
|
+
module Durt
|
7
|
+
class Issue < ApplicationRecord
|
8
|
+
before_validation :sanitize_summary
|
9
|
+
has_many :sessions, dependent: :destroy
|
10
|
+
belongs_to :project
|
11
|
+
|
12
|
+
scope :active, -> { where(active: true) }
|
13
|
+
|
14
|
+
def tracking?
|
15
|
+
!sessions.tracking.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def start_tracking!
|
19
|
+
if tracking?
|
20
|
+
puts 'Already tracking'
|
21
|
+
return sessions.tracking.last
|
22
|
+
end
|
23
|
+
|
24
|
+
sessions.create(open_at: Time.now)
|
25
|
+
end
|
26
|
+
|
27
|
+
def plugin
|
28
|
+
project.plugins.find { |p| p.plugin_name == source }
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop_tracking!
|
32
|
+
sessions.tracking.update_all(closed_at: Time.now)
|
33
|
+
end
|
34
|
+
|
35
|
+
def total_tracked_time
|
36
|
+
sessions.map(&:tracked_time).sum
|
37
|
+
end
|
38
|
+
|
39
|
+
def estimation_ratio
|
40
|
+
return Float::INFINITY if total_tracked_time.zero?
|
41
|
+
|
42
|
+
estimate.to_f / total_tracked_time
|
43
|
+
end
|
44
|
+
|
45
|
+
def overestimated?
|
46
|
+
estimation_ratio > 1
|
47
|
+
end
|
48
|
+
|
49
|
+
def underestimated?
|
50
|
+
estimation_ratio < 1
|
51
|
+
end
|
52
|
+
|
53
|
+
# Presenters
|
54
|
+
|
55
|
+
def stats
|
56
|
+
<<~MSG
|
57
|
+
|
58
|
+
-- #{self} --
|
59
|
+
Estimated: #{ChronicDuration.output(estimate || 0, format: :long)}.
|
60
|
+
Tracked: #{ChronicDuration.output(total_tracked_time, format: :long)}.
|
61
|
+
Estimation ratio: #{estimation_ratio} (#{estimation_result_label})
|
62
|
+
-----------------------------------
|
63
|
+
|
64
|
+
MSG
|
65
|
+
end
|
66
|
+
|
67
|
+
def puts_stats
|
68
|
+
puts stats
|
69
|
+
end
|
70
|
+
|
71
|
+
def estimation_result_label
|
72
|
+
return 'Underestimated' if underestimated?
|
73
|
+
return 'Overestimated' if overestimated?
|
74
|
+
|
75
|
+
'Who are you?'
|
76
|
+
end
|
77
|
+
|
78
|
+
def label
|
79
|
+
"[#{key}]"
|
80
|
+
end
|
81
|
+
|
82
|
+
def to_s
|
83
|
+
"#{label} #{summary}"
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def sanitize_summary
|
89
|
+
return unless summary
|
90
|
+
|
91
|
+
self.summary = summary.gsub(/[^\d\w\s,]/i, '')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'bug_tracker'
|
4
|
+
require 'jira-ruby'
|
5
|
+
|
6
|
+
module Durt
|
7
|
+
class JiraBugTracker < BugTracker
|
8
|
+
attr_accessor :client
|
9
|
+
|
10
|
+
def after_initialize
|
11
|
+
@client = JIRA::Client.new(@config)
|
12
|
+
end
|
13
|
+
|
14
|
+
def fetch_issues
|
15
|
+
fetched_issues = @client.Issue.jql(fetch_issues_query)
|
16
|
+
|
17
|
+
fetched_issues.map do |issue|
|
18
|
+
{
|
19
|
+
key: issue.key,
|
20
|
+
summary: issue.summary,
|
21
|
+
source: source_name,
|
22
|
+
project: project
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def fetch_statuses
|
28
|
+
statuses = @client.Status.all
|
29
|
+
|
30
|
+
statuses.map do |status|
|
31
|
+
Durt::Status
|
32
|
+
.find_or_create_by(source_id: status.id, source: 'Jira') do |s|
|
33
|
+
s.name = status.name
|
34
|
+
s.active = false
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# def estimate(key, estimation)
|
40
|
+
# comment(key, "Total seconds estimated for this task: #{estimation}")
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# def comment(key, content)
|
44
|
+
# issue = client.Issue.find(key)
|
45
|
+
#
|
46
|
+
# comment = issue.comments.build
|
47
|
+
# comment.save(body: content)
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
private
|
51
|
+
|
52
|
+
def fetch_issues_query
|
53
|
+
statuses_query = statuses.active.map { |s| "\"#{s.name}\"" }.join(', ')
|
54
|
+
query = "assignee=currentUser() AND status in (#{statuses_query})"
|
55
|
+
puts query
|
56
|
+
query
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'plugin'
|
4
|
+
|
5
|
+
module Durt
|
6
|
+
class JiraPlugin < Plugin
|
7
|
+
def self.demo_config
|
8
|
+
{
|
9
|
+
username: 'example@mail.com',
|
10
|
+
password: 'password',
|
11
|
+
site: 'http://project.atlassian.net:443/',
|
12
|
+
context_path: '',
|
13
|
+
auth_type: :basic
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def filter(_value)
|
18
|
+
bug_tracker.fetch_statuses
|
19
|
+
|
20
|
+
message = 'Select the statuses that you want to include:'
|
21
|
+
chosen_statuses = prompt.multi_select(message, statuses.to_choice_h)
|
22
|
+
|
23
|
+
statuses.update_all(active: false)
|
24
|
+
statuses.where(id: chosen_statuses).update_all(active: true)
|
25
|
+
end
|
26
|
+
|
27
|
+
def bug_tracker_class
|
28
|
+
Durt::JiraBugTracker
|
29
|
+
end
|
30
|
+
|
31
|
+
def statuses
|
32
|
+
bug_tracker.statuses
|
33
|
+
end
|
34
|
+
|
35
|
+
def prompt
|
36
|
+
TTY::Prompt.new
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'plugin'
|
4
|
+
|
5
|
+
module Durt
|
6
|
+
class NotifyPlugin < Plugin
|
7
|
+
def start(value)
|
8
|
+
time_tracker.start(value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def stop(value)
|
12
|
+
time_tracker.stop(value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def time_tracker_class
|
16
|
+
Durt::NotifyTracker
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|