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,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
@@ -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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bug_tracker'
4
+
5
+ module Durt
6
+ class LocalBugTracker < BugTracker; end
7
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'plugin'
4
+
5
+ module Durt
6
+ class LocalPlugin < Plugin
7
+ def bug_tracker_class
8
+ Durt::LocalBugTracker
9
+ end
10
+ end
11
+ 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