tempest_time 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/lib/tempest_time/api/request.rb +3 -1
- data/lib/tempest_time/cli.rb +4 -0
- data/lib/tempest_time/commands/issue/list.rb +3 -3
- data/lib/tempest_time/commands/issue/open.rb +4 -4
- data/lib/tempest_time/commands/issue.rb +2 -2
- data/lib/tempest_time/commands/submit.rb +0 -1
- data/lib/tempest_time/commands/timer/delete.rb +33 -0
- data/lib/tempest_time/commands/timer/list.rb +59 -0
- data/lib/tempest_time/commands/timer/pause.rb +26 -0
- data/lib/tempest_time/commands/timer/start.rb +27 -0
- data/lib/tempest_time/commands/timer/status.rb +33 -0
- data/lib/tempest_time/commands/timer/track.rb +44 -0
- data/lib/tempest_time/commands/timer.rb +48 -0
- data/lib/tempest_time/commands/track.rb +2 -3
- data/lib/tempest_time/helpers/time_helper.rb +9 -0
- data/lib/tempest_time/models/report.rb +0 -1
- data/lib/tempest_time/models/timer.rb +81 -0
- data/lib/tempest_time/version.rb +3 -1
- metadata +9 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bee4c127b0d41d0f31cf49f9b3291c5948060eb44a8e9835720cb94ab9ffd123
|
4
|
+
data.tar.gz: 7d8102ca79d54c07b6ade649135f5a545d6a6e2297564420e1cd762b18980b4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6db29dd83c233f730e47f8226292f305ae8c99c8f640846f271314ab3365c59e0ebbed337910fbf7b28971ccde1e5e645b1f8b19ea0e3cdfd368a2c1b34ec0e2
|
7
|
+
data.tar.gz: c8d4feac6d657e579d0382e72331dd18a33b5a13e024c12baf67c7d7131c2acc6087e95ebceb53a0199cda97888e6e7976462bec2fd4c2992f70613b1c37c2cb
|
data/.gitignore
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'httparty'
|
2
4
|
require 'json'
|
3
5
|
|
@@ -9,7 +11,7 @@ module TempestTime
|
|
9
11
|
class Request
|
10
12
|
include HTTParty
|
11
13
|
|
12
|
-
DATE_FORMAT = "%Y-%m-%d"
|
14
|
+
DATE_FORMAT = "%Y-%m-%d"
|
13
15
|
|
14
16
|
def initialize(*args)
|
15
17
|
self.class.base_uri credentials.fetch(:url)
|
data/lib/tempest_time/cli.rb
CHANGED
@@ -81,6 +81,10 @@ module TempestTime
|
|
81
81
|
TempestTime::Commands::Track.new(time, issues, options).execute
|
82
82
|
end
|
83
83
|
|
84
|
+
require_relative 'commands/timer'
|
85
|
+
register TempestTime::Commands::Timer,
|
86
|
+
'timer', 'timer [SUBCOMMAND]', 'Start, Stop, Delete, Submit Timers'
|
87
|
+
|
84
88
|
map log: :track
|
85
89
|
end
|
86
90
|
end
|
@@ -25,14 +25,14 @@ module TempestTime
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def browser_prompt(issues)
|
28
|
-
abort if prompt.no?('Open
|
29
|
-
|
28
|
+
abort if prompt.no?('Open any issues in your browser?')
|
29
|
+
selections = prompt.multi_select(
|
30
30
|
'Select an issue to open in browser, or press ^C to quit.',
|
31
31
|
issues.map(&:key),
|
32
32
|
per_page: 5
|
33
33
|
)
|
34
34
|
require_relative 'open'
|
35
|
-
Open.new(
|
35
|
+
Open.new(selections).execute
|
36
36
|
end
|
37
37
|
|
38
38
|
def format_output(issues)
|
@@ -7,13 +7,13 @@ module TempestTime
|
|
7
7
|
module Commands
|
8
8
|
class Issue
|
9
9
|
class Open < TempestTime::Command
|
10
|
-
def initialize(
|
11
|
-
@
|
12
|
-
@
|
10
|
+
def initialize(issues)
|
11
|
+
@issues = issues.map(&:upcase)
|
12
|
+
@issues = [automatic_issue] if issues.empty?
|
13
13
|
end
|
14
14
|
|
15
15
|
def execute(input: $stdin, output: $stdout)
|
16
|
-
command.run("open #{url(
|
16
|
+
@issues.each { |issue| command.run("open #{url(issue)}") }
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
@@ -15,9 +15,9 @@ module TempestTime
|
|
15
15
|
end
|
16
16
|
|
17
17
|
desc 'open', 'Open an issue in your browser. (Default: current branch)'
|
18
|
-
def open(
|
18
|
+
def open(*issues)
|
19
19
|
require_relative 'issue/open'
|
20
|
-
TempestTime::Commands::Issue::Open.new(
|
20
|
+
TempestTime::Commands::Issue::Open.new(issues).execute
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
require_relative '../../models/timer'
|
5
|
+
|
6
|
+
module TempestTime
|
7
|
+
module Commands
|
8
|
+
class Timer
|
9
|
+
class Delete < TempestTime::Command
|
10
|
+
def initialize(issue)
|
11
|
+
@issue = issue || automatic_issue
|
12
|
+
@timer = TempestTime::Models::Timer.new(@issue)
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(input: $stdin, output: $stdout)
|
16
|
+
timer.delete ? deleted_message : no_timer_message
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :issue, :timer
|
22
|
+
|
23
|
+
def deleted_message
|
24
|
+
prompt.say('baleeted')
|
25
|
+
end
|
26
|
+
|
27
|
+
def no_timer_message
|
28
|
+
prompt.say('noleeted')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './status'
|
4
|
+
require_relative '../../command'
|
5
|
+
require_relative '../../models/timer'
|
6
|
+
|
7
|
+
module TempestTime
|
8
|
+
module Commands
|
9
|
+
class Timer
|
10
|
+
class List < TempestTime::Command
|
11
|
+
def execute(input: $stdin, output: $stdout)
|
12
|
+
all_timers.each do |timer|
|
13
|
+
TempestTime::Commands::Timer::Status.new(timer.issue).execute
|
14
|
+
end
|
15
|
+
issue = prompt.select(
|
16
|
+
'Please select a timer to toggle / track, or enter ^C to exit.',
|
17
|
+
all_timers.map(&:issue)
|
18
|
+
)
|
19
|
+
action = action_prompt(timer(issue))
|
20
|
+
take_action(issue, action)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def all_timers
|
26
|
+
@all_timers ||= TempestTime::Models::Timer.all_timers
|
27
|
+
end
|
28
|
+
|
29
|
+
def timer(issue)
|
30
|
+
all_timers.detect { |timer| timer.issue == issue }
|
31
|
+
end
|
32
|
+
|
33
|
+
def action_prompt(timer)
|
34
|
+
if timer.running?
|
35
|
+
prompt.select('', %w[Pause Track])
|
36
|
+
else
|
37
|
+
prompt.select('', %w[Resume Track])
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def take_action(issue, action)
|
42
|
+
case action
|
43
|
+
when 'Pause'
|
44
|
+
require_relative './pause'
|
45
|
+
TempestTime::Commands::Timer::Pause.new(issue).execute
|
46
|
+
when 'Resume'
|
47
|
+
require_relative './start'
|
48
|
+
TempestTime::Commands::Timer::Start.new(issue).execute
|
49
|
+
when 'Track'
|
50
|
+
require_relative './track'
|
51
|
+
TempestTime::Commands::Timer::Track.new(issue).execute
|
52
|
+
else
|
53
|
+
abort
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
require_relative '../../models/timer'
|
5
|
+
|
6
|
+
module TempestTime
|
7
|
+
module Commands
|
8
|
+
class Timer
|
9
|
+
class Pause < TempestTime::Command
|
10
|
+
def initialize(issue)
|
11
|
+
@issue = issue || automatic_issue
|
12
|
+
@timer = TempestTime::Models::Timer.new(@issue)
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(input: $stdin, output: $stdout)
|
16
|
+
timer.pause
|
17
|
+
TempestTime::Commands::Timer::Status.new(issue).execute
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :issue, :timer
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './status'
|
4
|
+
require_relative '../../command'
|
5
|
+
require_relative '../../models/timer'
|
6
|
+
|
7
|
+
module TempestTime
|
8
|
+
module Commands
|
9
|
+
class Timer
|
10
|
+
class Start < TempestTime::Command
|
11
|
+
def initialize(issue)
|
12
|
+
@issue = issue || automatic_issue
|
13
|
+
@timer = TempestTime::Models::Timer.new(@issue)
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute(input: $stdin, output: $stdout)
|
17
|
+
timer.start
|
18
|
+
TempestTime::Commands::Timer::Status.new(issue).execute
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :issue, :timer
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
require_relative '../../models/timer'
|
5
|
+
|
6
|
+
module TempestTime
|
7
|
+
module Commands
|
8
|
+
class Timer
|
9
|
+
class Status < TempestTime::Command
|
10
|
+
def initialize(issue)
|
11
|
+
@issue = issue
|
12
|
+
@timer = TempestTime::Models::Timer.new(issue)
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(input: $stdin, output: $stdout)
|
16
|
+
prompt.say("#{issue}: #{status_message}")
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :issue, :timer
|
22
|
+
|
23
|
+
def status_message
|
24
|
+
if timer.running?
|
25
|
+
pastel.green("#{formatted_time_long(timer.runtime)} running")
|
26
|
+
else
|
27
|
+
pastel.yellow("#{formatted_time_long(timer.runtime)} paused")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../track'
|
4
|
+
require_relative '../../command'
|
5
|
+
require_relative '../../models/timer'
|
6
|
+
|
7
|
+
module TempestTime
|
8
|
+
module Commands
|
9
|
+
class Timer
|
10
|
+
class Track < TempestTime::Command
|
11
|
+
attr_reader :timer, :issue
|
12
|
+
|
13
|
+
def initialize(issue)
|
14
|
+
@issue = issue || automatic_issue
|
15
|
+
@timer = TempestTime::Models::Timer.new(@issue)
|
16
|
+
end
|
17
|
+
|
18
|
+
def execute(input: $stdin, output: $stdout)
|
19
|
+
abort(pastel.red("No timer for #{issue}!")) unless timer.exists?
|
20
|
+
timer.pause
|
21
|
+
track_time
|
22
|
+
timer.delete
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def track_time
|
28
|
+
time = prompt.ask(
|
29
|
+
"How much time should be logged to #{issue}?",
|
30
|
+
default: formatted_time_for_input(timer.runtime)
|
31
|
+
)
|
32
|
+
billable = prompt.yes?('Is this time billable?')
|
33
|
+
message = prompt.ask('Add a message. (optional)')
|
34
|
+
Commands::Track.new(
|
35
|
+
parsed_time(time),
|
36
|
+
[issue],
|
37
|
+
'message' => message,
|
38
|
+
'billable' => billable
|
39
|
+
).execute
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module TempestTime
|
6
|
+
module Commands
|
7
|
+
class Timer < Thor
|
8
|
+
|
9
|
+
namespace :timer
|
10
|
+
|
11
|
+
desc 'start [ISSUE]', 'Start a new timer, or continue a paused timer.'
|
12
|
+
def start(issue = nil)
|
13
|
+
require_relative 'timer/start'
|
14
|
+
TempestTime::Commands::Timer::Start.new(issue).execute
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'pause [ISSUE]', 'Pause a timer.'
|
18
|
+
def pause(issue = nil)
|
19
|
+
require_relative 'timer/pause'
|
20
|
+
TempestTime::Commands::Timer::Pause.new(issue).execute
|
21
|
+
end
|
22
|
+
|
23
|
+
desc 'track [ISSUE]', 'Stop and track a timer.'
|
24
|
+
def track(issue = nil)
|
25
|
+
require_relative 'timer/track'
|
26
|
+
TempestTime::Commands::Timer::Track.new(issue).execute
|
27
|
+
end
|
28
|
+
|
29
|
+
desc 'delete [ISSUE]', 'Delete current timer.'
|
30
|
+
def delete(issue = nil)
|
31
|
+
require_relative 'timer/delete'
|
32
|
+
TempestTime::Commands::Timer::Delete.new(issue).execute
|
33
|
+
end
|
34
|
+
|
35
|
+
desc 'status [ISSUE]', 'Display timer status.'
|
36
|
+
def status(issue = nil)
|
37
|
+
require_relative 'timer/status'
|
38
|
+
TempestTime::Commands::Timer::Status.new(issue).execute
|
39
|
+
end
|
40
|
+
|
41
|
+
desc 'list', 'Display all timers.'
|
42
|
+
def list
|
43
|
+
require_relative 'timer/list'
|
44
|
+
TempestTime::Commands::Timer::List.new.execute
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -2,9 +2,8 @@
|
|
2
2
|
|
3
3
|
require_relative '../command'
|
4
4
|
require_relative '../helpers/time_helper'
|
5
|
-
|
6
|
-
require_relative '../api/tempo_api/requests/create_worklog'
|
7
5
|
require_relative '../api/jira_api/requests/get_issue'
|
6
|
+
require_relative '../api/tempo_api/requests/create_worklog'
|
8
7
|
|
9
8
|
module TempestTime
|
10
9
|
module Commands
|
@@ -27,7 +26,7 @@ module TempestTime
|
|
27
26
|
end
|
28
27
|
|
29
28
|
issues.each do |issue|
|
30
|
-
track_time(time, @options.merge(issue
|
29
|
+
track_time(time, @options.merge('issue' => issue))
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
@@ -21,6 +21,15 @@ module TempestTime
|
|
21
21
|
"#{(seconds / 3600.to_f).round(2)} hours"
|
22
22
|
end
|
23
23
|
|
24
|
+
def formatted_time_for_input(seconds)
|
25
|
+
return "#{(seconds / 60).to_i}m" if seconds < 3600
|
26
|
+
"#{(seconds / 3600.to_f).round(2)}h"
|
27
|
+
end
|
28
|
+
|
29
|
+
def formatted_time_long(seconds)
|
30
|
+
Time.at(seconds).utc.strftime("%H:%M:%S")
|
31
|
+
end
|
32
|
+
|
24
33
|
def formatted_date_range(start_date, end_date)
|
25
34
|
return formatted_date(start_date) if end_date.nil?
|
26
35
|
"#{formatted_date(start_date)} - #{formatted_date(end_date)}"
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module TempestTime
|
6
|
+
module Models
|
7
|
+
class Timer
|
8
|
+
TEMP_DIR = '/tmp/timer/logs'
|
9
|
+
FILE_EXT = '.timer'
|
10
|
+
PREFIX_SEPARATOR = '___'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def all_timers
|
14
|
+
issues = Dir.glob("#{TEMP_DIR}/*#{FILE_EXT}").map do |file|
|
15
|
+
file.delete(TEMP_DIR).split(PREFIX_SEPARATOR).first
|
16
|
+
end
|
17
|
+
|
18
|
+
issues.uniq.sort.map { |issue| new(issue) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :issue
|
23
|
+
|
24
|
+
def initialize(issue)
|
25
|
+
ensure_tmp_dir
|
26
|
+
@issue = issue || automatic_issue
|
27
|
+
end
|
28
|
+
|
29
|
+
def start
|
30
|
+
return false if running?
|
31
|
+
Tempfile.create([log_file_prefix, FILE_EXT], TEMP_DIR)
|
32
|
+
end
|
33
|
+
|
34
|
+
def pause
|
35
|
+
log_files.each do |log|
|
36
|
+
FileUtils.touch(log) if log_running?(log)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete
|
41
|
+
return false unless exists?
|
42
|
+
log_files.each { |log| File.unlink log }
|
43
|
+
end
|
44
|
+
|
45
|
+
def runtime
|
46
|
+
@logs ||= log_files.each_with_object([]) do |log, array|
|
47
|
+
start_time = File.birthtime(log)
|
48
|
+
end_time = log_running?(log) ? Time.now : File.mtime(log)
|
49
|
+
|
50
|
+
array << (end_time - start_time)
|
51
|
+
end.sum
|
52
|
+
end
|
53
|
+
|
54
|
+
def running?
|
55
|
+
log_files.any? { |log| log_running?(log) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def exists?
|
59
|
+
log_files.any?
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def log_file_prefix
|
65
|
+
"#{issue}#{PREFIX_SEPARATOR}"
|
66
|
+
end
|
67
|
+
|
68
|
+
def log_running?(log)
|
69
|
+
File.birthtime(log).eql? File.mtime(log)
|
70
|
+
end
|
71
|
+
|
72
|
+
def log_files
|
73
|
+
@log_files ||= Dir.glob("#{TEMP_DIR}/#{issue}*#{FILE_EXT}")
|
74
|
+
end
|
75
|
+
|
76
|
+
def ensure_tmp_dir
|
77
|
+
FileUtils.mkdir_p TEMP_DIR unless File.directory? TEMP_DIR
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/tempest_time/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tempest_time
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Devan Hurst
|
@@ -463,11 +463,19 @@ files:
|
|
463
463
|
- lib/tempest_time/commands/teams/add.rb
|
464
464
|
- lib/tempest_time/commands/teams/delete.rb
|
465
465
|
- lib/tempest_time/commands/teams/edit.rb
|
466
|
+
- lib/tempest_time/commands/timer.rb
|
467
|
+
- lib/tempest_time/commands/timer/delete.rb
|
468
|
+
- lib/tempest_time/commands/timer/list.rb
|
469
|
+
- lib/tempest_time/commands/timer/pause.rb
|
470
|
+
- lib/tempest_time/commands/timer/start.rb
|
471
|
+
- lib/tempest_time/commands/timer/status.rb
|
472
|
+
- lib/tempest_time/commands/timer/track.rb
|
466
473
|
- lib/tempest_time/commands/track.rb
|
467
474
|
- lib/tempest_time/helpers/formatting_helper.rb
|
468
475
|
- lib/tempest_time/helpers/git_helper.rb
|
469
476
|
- lib/tempest_time/helpers/time_helper.rb
|
470
477
|
- lib/tempest_time/models/report.rb
|
478
|
+
- lib/tempest_time/models/timer.rb
|
471
479
|
- lib/tempest_time/setting.rb
|
472
480
|
- lib/tempest_time/settings/app.rb
|
473
481
|
- lib/tempest_time/settings/authorization.rb
|