tempest-time 0.5.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/.gitignore +10 -0
- data/.ruby-version +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +133 -0
- data/LICENSE +21 -0
- data/README.md +8 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/tempest +18 -0
- data/lib/tempest_time.rb +4 -0
- data/lib/tempest_time/api/authorization.rb +17 -0
- data/lib/tempest_time/api/jira_api/authorization.rb +27 -0
- data/lib/tempest_time/api/jira_api/models/issue.rb +28 -0
- data/lib/tempest_time/api/jira_api/request.rb +40 -0
- data/lib/tempest_time/api/jira_api/requests/get_issue.rb +29 -0
- data/lib/tempest_time/api/jira_api/requests/get_user_issues.rb +36 -0
- data/lib/tempest_time/api/jira_api/response.rb +5 -0
- data/lib/tempest_time/api/jira_api/responses/get_issue.rb +16 -0
- data/lib/tempest_time/api/jira_api/responses/get_user_issues.rb +14 -0
- data/lib/tempest_time/api/request.rb +87 -0
- data/lib/tempest_time/api/response.rb +34 -0
- data/lib/tempest_time/api/tempo_api/authorization.rb +23 -0
- data/lib/tempest_time/api/tempo_api/models/worklog.rb +36 -0
- data/lib/tempest_time/api/tempo_api/request.rb +35 -0
- data/lib/tempest_time/api/tempo_api/requests/create_worklog.rb +51 -0
- data/lib/tempest_time/api/tempo_api/requests/delete_worklog.rb +29 -0
- data/lib/tempest_time/api/tempo_api/requests/get_worklog.rb +29 -0
- data/lib/tempest_time/api/tempo_api/requests/list_worklogs.rb +41 -0
- data/lib/tempest_time/api/tempo_api/requests/report.rb +0 -0
- data/lib/tempest_time/api/tempo_api/requests/submit_timesheet.rb +49 -0
- data/lib/tempest_time/api/tempo_api/response.rb +5 -0
- data/lib/tempest_time/api/tempo_api/responses/create_worklog.rb +28 -0
- data/lib/tempest_time/api/tempo_api/responses/delete_worklog.rb +8 -0
- data/lib/tempest_time/api/tempo_api/responses/get_worklog.rb +22 -0
- data/lib/tempest_time/api/tempo_api/responses/list_worklogs.rb +40 -0
- data/lib/tempest_time/api/tempo_api/responses/submit_timesheet.rb +13 -0
- data/lib/tempest_time/cli.rb +92 -0
- data/lib/tempest_time/command.rb +60 -0
- data/lib/tempest_time/commands/config.rb +30 -0
- data/lib/tempest_time/commands/config/edit.rb +38 -0
- data/lib/tempest_time/commands/config/setup.rb +55 -0
- data/lib/tempest_time/commands/delete.rb +31 -0
- data/lib/tempest_time/commands/issues.rb +37 -0
- data/lib/tempest_time/commands/list.rb +32 -0
- data/lib/tempest_time/commands/report.rb +83 -0
- data/lib/tempest_time/commands/submit.rb +26 -0
- data/lib/tempest_time/commands/teams.rb +30 -0
- data/lib/tempest_time/commands/teams/add.rb +29 -0
- data/lib/tempest_time/commands/teams/delete.rb +32 -0
- data/lib/tempest_time/commands/teams/edit.rb +49 -0
- data/lib/tempest_time/commands/track.rb +70 -0
- data/lib/tempest_time/helpers/formatting_helper.rb +16 -0
- data/lib/tempest_time/helpers/time_helper.rb +63 -0
- data/lib/tempest_time/models/report.rb +58 -0
- data/lib/tempest_time/services/generate_report.rb +54 -0
- data/lib/tempest_time/setting.rb +47 -0
- data/lib/tempest_time/settings/authorization.rb +19 -0
- data/lib/tempest_time/settings/teams.rb +19 -0
- data/lib/tempest_time/templates/config/.gitkeep +1 -0
- data/lib/tempest_time/templates/config/auth/.gitkeep +1 -0
- data/lib/tempest_time/templates/config/setup/.gitkeep +1 -0
- data/lib/tempest_time/templates/config/teams/.gitkeep +1 -0
- data/lib/tempest_time/templates/delete/.gitkeep +1 -0
- data/lib/tempest_time/templates/issue/.gitkeep +1 -0
- data/lib/tempest_time/templates/issues/.gitkeep +1 -0
- data/lib/tempest_time/templates/list/.gitkeep +1 -0
- data/lib/tempest_time/templates/report/.gitkeep +1 -0
- data/lib/tempest_time/templates/setup/.gitkeep +1 -0
- data/lib/tempest_time/templates/submit/.gitkeep +1 -0
- data/lib/tempest_time/templates/teams/.gitkeep +1 -0
- data/lib/tempest_time/templates/track/.gitkeep +1 -0
- data/lib/tempest_time/templates/view/.gitkeep +1 -0
- data/lib/tempest_time/version.rb +3 -0
- data/tempest-time.gemspec +62 -0
- metadata +517 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
require_relative '../response'
|
2
|
+
require_relative '../../../helpers/time_helper'
|
3
|
+
|
4
|
+
module TempoAPI
|
5
|
+
module Responses
|
6
|
+
class CreateWorklog < TempoAPI::Response
|
7
|
+
include TempestTime::Helpers::TimeHelper
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def worklog_id
|
12
|
+
@worklog_id ||= raw_response['tempoWorklogId']
|
13
|
+
end
|
14
|
+
|
15
|
+
def seconds
|
16
|
+
@seconds ||= raw_response['timeSpentSeconds']
|
17
|
+
end
|
18
|
+
|
19
|
+
def issue_key
|
20
|
+
@issue_key ||= raw_response['issue']['key']
|
21
|
+
end
|
22
|
+
|
23
|
+
def success_message
|
24
|
+
"Worklog #{worklog_id} created!"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative '../response'
|
2
|
+
|
3
|
+
module TempoAPI
|
4
|
+
module Responses
|
5
|
+
class GetWorklog < TempoAPI::Response
|
6
|
+
private
|
7
|
+
|
8
|
+
def worklog
|
9
|
+
@worklog ||= TempoAPI::Models::Worklog.new(
|
10
|
+
id: raw_response['tempoWorklogId'],
|
11
|
+
issue: raw_response.dig('issue', 'key'),
|
12
|
+
seconds: raw_response['timeSpentSeconds'],
|
13
|
+
description: raw_response['description']
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
def success_message
|
18
|
+
worklog.to_s
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative '../response'
|
2
|
+
require_relative '../models/worklog'
|
3
|
+
require_relative '../../../helpers/time_helper'
|
4
|
+
|
5
|
+
module TempoAPI
|
6
|
+
module Responses
|
7
|
+
class ListWorklogs < TempoAPI::Response
|
8
|
+
include TempestTime::Helpers::TimeHelper
|
9
|
+
|
10
|
+
attr_reader :worklogs
|
11
|
+
|
12
|
+
def worklogs
|
13
|
+
@worklogs ||= raw_response['results'].map do |worklog|
|
14
|
+
TempoAPI::Models::Worklog.new(
|
15
|
+
id: worklog['tempoWorklogId'],
|
16
|
+
issue: worklog.dig('issue', 'key'),
|
17
|
+
seconds: worklog['timeSpentSeconds'],
|
18
|
+
description: worklog['description']
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def success_message
|
26
|
+
output = ""
|
27
|
+
output << worklogs_output
|
28
|
+
output << "\nTOTAL TIME LOGGED: #{total_hours_spent} hours."
|
29
|
+
end
|
30
|
+
|
31
|
+
def worklogs_output
|
32
|
+
worklogs.map(&:to_s).join("\n")
|
33
|
+
end
|
34
|
+
|
35
|
+
def total_hours_spent
|
36
|
+
worklogs.map(&:hours).reduce(:+)&.round(2) || 0
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'git'
|
3
|
+
|
4
|
+
module TempestTime
|
5
|
+
class CLI < Thor
|
6
|
+
# Error raised by this runner
|
7
|
+
Error = Class.new(StandardError)
|
8
|
+
|
9
|
+
desc 'version', 'Show version number.'
|
10
|
+
def version
|
11
|
+
require_relative 'version'
|
12
|
+
puts "v#{TempestTime::VERSION}"
|
13
|
+
end
|
14
|
+
map %w(--version -v) => :version
|
15
|
+
|
16
|
+
require_relative 'commands/config'
|
17
|
+
register TempestTime::Commands::Config,
|
18
|
+
'config', 'config [SUBCOMMAND]',
|
19
|
+
'Setup or modify user settings.'
|
20
|
+
|
21
|
+
require_relative 'commands/teams'
|
22
|
+
register TempestTime::Commands::Teams,
|
23
|
+
'teams', 'teams [SUBCOMMAND]',
|
24
|
+
'Add or modify teams.'
|
25
|
+
|
26
|
+
desc 'list [DATE]', 'List worklogs for given date. (Defaults to today.)'
|
27
|
+
long_desc <<-LONGDESC
|
28
|
+
`tempest list` will list a day's worklogs.\n
|
29
|
+
e.g. `tempest list today`\n
|
30
|
+
e.g. `tempest list yesterday`\n
|
31
|
+
e.g. `tempest list 2019-01-31`\n
|
32
|
+
e.g. `tempest list 2019-01-31 --user=jsmith`
|
33
|
+
LONGDESC
|
34
|
+
def list(date = nil)
|
35
|
+
require_relative 'commands/list'
|
36
|
+
TempestTime::Commands::List.new(date, options).execute
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
desc 'submit', 'Submit your timesheet to a supervisor.'
|
41
|
+
def submit(*)
|
42
|
+
require_relative 'commands/submit'
|
43
|
+
TempestTime::Commands::Submit.new(options).execute
|
44
|
+
end
|
45
|
+
|
46
|
+
desc 'report', 'Generate a user or team report.'
|
47
|
+
option :week, aliases: '-w', type: :numeric
|
48
|
+
option :team, aliases: '-t', type: :string
|
49
|
+
def report(*users)
|
50
|
+
require_relative 'commands/report'
|
51
|
+
TempestTime::Commands::Report.new(users, options).execute
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'delete [WORKLOG(S)]', 'Delete worklogs.'
|
55
|
+
long_desc <<-LONGDESC
|
56
|
+
`tempest_time delete` will delete the specified worklogs.\n
|
57
|
+
e.g. `tempest delete 12345`\n
|
58
|
+
e.g. `tempest delete 12345 12346 12347`\n
|
59
|
+
LONGDESC
|
60
|
+
option :autoconfirm, type: :boolean
|
61
|
+
def delete(*worklogs)
|
62
|
+
require_relative 'commands/delete'
|
63
|
+
TempestTime::Commands::Delete.new(worklogs, options).execute
|
64
|
+
end
|
65
|
+
|
66
|
+
desc 'issues', "View a list of a user's assigned tickets. (Defaults to you.)"
|
67
|
+
def issues(user = nil)
|
68
|
+
require_relative 'commands/issues'
|
69
|
+
TempestTime::Commands::Issues.new(user, options).execute
|
70
|
+
end
|
71
|
+
|
72
|
+
desc "track [TIME] [TICKET(S)]", 'Track time to Tempo.'
|
73
|
+
long_desc <<-LONGDESC
|
74
|
+
`tempest track` or `tempest log` will track the specified number of hours or minutes to the ticket(s) specified.\n
|
75
|
+
If not specified, it will check the name of the current git branch and automatically track the logged time to that ticket, if found.\n
|
76
|
+
You can also split a bank of time evenly across multiple tickets with the --split flag.\n
|
77
|
+
e.g. tempest track 1.5h BCIT-1 --message='Tracking 1.5 hours.'\n
|
78
|
+
e.g. tempest log 90m BCIT-1 BCIT-2 --message='Tracking 90 minutes.'\n
|
79
|
+
e.g. tempest track 3h BCIT-1 BCIT-2 BCIT-3 --message='Tracking 1 hour.'\n
|
80
|
+
LONGDESC
|
81
|
+
option :message, aliases: '-m', type: :string
|
82
|
+
option :date, aliases: '-d', type: :string
|
83
|
+
option :remaining, aliases: '-r', type: :string
|
84
|
+
option :billable, aliases: '-b', type: :boolean, default: true
|
85
|
+
option :split, aliases: '-s', type: :boolean, default: false
|
86
|
+
option :autoconfirm, type: :boolean, default: false
|
87
|
+
def track(time, *tickets)
|
88
|
+
require_relative 'commands/track'
|
89
|
+
TempestTime::Commands::Track.new(time, tickets, options).execute
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module TempestTime
|
6
|
+
class Command
|
7
|
+
extend Forwardable
|
8
|
+
|
9
|
+
def_delegators :command, :run
|
10
|
+
|
11
|
+
def execute(*)
|
12
|
+
raise(
|
13
|
+
NotImplementedError,
|
14
|
+
"#{self.class}##{__method__} must be implemented"
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def command
|
19
|
+
require 'tty-command'
|
20
|
+
TTY::Command.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def pastel(**options)
|
24
|
+
require 'pastel'
|
25
|
+
Pastel.new(options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def prompt(**options)
|
29
|
+
require 'tty-prompt'
|
30
|
+
TTY::Prompt.new(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def spinner
|
34
|
+
require 'tty-spinner'
|
35
|
+
TTY::Spinner
|
36
|
+
end
|
37
|
+
|
38
|
+
def table
|
39
|
+
require 'tty-table'
|
40
|
+
TTY::Table
|
41
|
+
end
|
42
|
+
|
43
|
+
def with_spinner(message, format = :pong)
|
44
|
+
s = spinner.new(":spinner #{message}", format: format)
|
45
|
+
s.auto_spin
|
46
|
+
yield(s)
|
47
|
+
end
|
48
|
+
|
49
|
+
def with_success_fail_spinner(message, format = :spin_3)
|
50
|
+
s = spinner.new(":spinner #{message}", format: format)
|
51
|
+
s.auto_spin
|
52
|
+
response = yield
|
53
|
+
if response.success?
|
54
|
+
s.success(pastel.green(response.message))
|
55
|
+
else
|
56
|
+
s.error(pastel.red(response.message))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
|
5
|
+
module TempestTime
|
6
|
+
module Commands
|
7
|
+
class Config < Thor
|
8
|
+
|
9
|
+
namespace :config
|
10
|
+
|
11
|
+
desc 'setup', 'Set up Tempest with your credentials.'
|
12
|
+
def setup(*)
|
13
|
+
require_relative 'config/setup'
|
14
|
+
TempestTime::Commands::Config::Setup.new(options).execute
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'edit', 'Modify your user credentials.'
|
18
|
+
method_option :help, aliases: '-h', type: :boolean,
|
19
|
+
desc: 'Display usage information'
|
20
|
+
def edit(*)
|
21
|
+
if options[:help]
|
22
|
+
invoke :help, ['auth']
|
23
|
+
else
|
24
|
+
require_relative 'config/edit'
|
25
|
+
TempestTime::Commands::Config::Edit.new(options).execute
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
require_relative '../../settings/authorization'
|
5
|
+
|
6
|
+
module TempestTime
|
7
|
+
module Commands
|
8
|
+
class Config
|
9
|
+
class Edit < TempestTime::Command
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(input: $stdin, output: $stdout)
|
15
|
+
settings = TempestTime::Settings::Authorization
|
16
|
+
setting = prompt.select(
|
17
|
+
"Which #{pastel.green('setting')} would you like to edit?",
|
18
|
+
settings.keys
|
19
|
+
)
|
20
|
+
|
21
|
+
new_value = prompt.ask(
|
22
|
+
"Enter the #{pastel.green('new value')}.",
|
23
|
+
default: settings.read(setting)
|
24
|
+
)
|
25
|
+
|
26
|
+
if new_value != settings.read(setting)
|
27
|
+
settings.update(setting, new_value)
|
28
|
+
prompt.say(pastel.green('Setting updated!'))
|
29
|
+
else
|
30
|
+
prompt.say('Nothing changed.')
|
31
|
+
end
|
32
|
+
|
33
|
+
execute if prompt.yes?('Keep editing?')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../command'
|
4
|
+
require_relative '../../settings/authorization'
|
5
|
+
|
6
|
+
module TempestTime
|
7
|
+
module Commands
|
8
|
+
class Config
|
9
|
+
class Setup < TempestTime::Command
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(input: $stdin, output: $stdout)
|
15
|
+
email = prompt.ask(
|
16
|
+
'Please enter your Atlassian ID. '\
|
17
|
+
'This is typically your login email.'
|
18
|
+
)
|
19
|
+
username = prompt.ask(
|
20
|
+
'Please enter your Atlassian username.'
|
21
|
+
)
|
22
|
+
subdomain = prompt.ask(
|
23
|
+
'Please enter your Atlassian subdomain. '\
|
24
|
+
'i.e. [THIS VALUE].atlassian.net'
|
25
|
+
)
|
26
|
+
jira_token = prompt.ask(
|
27
|
+
'Please enter your Atlassian API token. '\
|
28
|
+
'Your token can be generated at https://id.atlassian.com/manage/api-tokens'
|
29
|
+
)
|
30
|
+
tempo_token = prompt.ask(
|
31
|
+
'Please enter your Tempo API token. '\
|
32
|
+
"Your token can be generated through your worksheet's settings page."
|
33
|
+
)
|
34
|
+
|
35
|
+
if [email, username, subdomain, jira_token, tempo_token].any?(&:nil?)
|
36
|
+
abort(
|
37
|
+
pastel.red('Setup failed!') + ' ' +
|
38
|
+
'One or more credentials were missing. Please try again.'
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
authorization = TempestTime::Settings::Authorization
|
43
|
+
|
44
|
+
authorization.update('email', email)
|
45
|
+
authorization.update('username', username)
|
46
|
+
authorization.update('subdomain', subdomain)
|
47
|
+
authorization.update('jira_token', jira_token)
|
48
|
+
authorization.update('tempo_token', tempo_token)
|
49
|
+
|
50
|
+
puts pastel.green('Setup complete!')
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative '../api/tempo_api/requests/delete_worklog'
|
5
|
+
|
6
|
+
module TempestTime
|
7
|
+
module Commands
|
8
|
+
class Delete < TempestTime::Command
|
9
|
+
def initialize(worklogs, options)
|
10
|
+
@worklogs = worklogs
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(input: $stdin, output: $stdout)
|
15
|
+
pluralized = @worklogs.length > 1 ? 'worklogs' : 'worklog'
|
16
|
+
confirm_message =
|
17
|
+
"Delete #{pluralized} #{pastel.green(@worklogs.join(', '))}?"
|
18
|
+
abort unless prompt.yes?(confirm_message)
|
19
|
+
@worklogs.each { |worklog| delete_worklog(worklog) }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def delete_worklog(worklog)
|
25
|
+
with_success_fail_spinner("Deleting worklog #{worklog}...", :spin_3) do
|
26
|
+
TempoAPI::Requests::DeleteWorklog.new(worklog).send_request
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../command'
|
4
|
+
require_relative '../api/jira_api/requests/get_user_issues'
|
5
|
+
|
6
|
+
module TempestTime
|
7
|
+
module Commands
|
8
|
+
class Issues < TempestTime::Command
|
9
|
+
def initialize(user, options)
|
10
|
+
@user = user
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(input: $stdin, output: $stdout)
|
15
|
+
request = JiraAPI::Requests::GetUserIssues.new(@user)
|
16
|
+
with_spinner("Getting issues for #{request.requested_user}") do |spinner|
|
17
|
+
response = request.send_request
|
18
|
+
spinner.stop
|
19
|
+
puts format_output(response.issues)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def format_output(issues)
|
26
|
+
table.new(
|
27
|
+
%w[Status Issue Summary],
|
28
|
+
issues.map { |issue| row(issue) }
|
29
|
+
).render(:ascii, padding: [0, 1])
|
30
|
+
end
|
31
|
+
|
32
|
+
def row(issue)
|
33
|
+
[issue.status, issue.key, issue.summary]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|