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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +133 -0
  7. data/LICENSE +21 -0
  8. data/README.md +8 -0
  9. data/Rakefile +10 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/bin/tempest +18 -0
  13. data/lib/tempest_time.rb +4 -0
  14. data/lib/tempest_time/api/authorization.rb +17 -0
  15. data/lib/tempest_time/api/jira_api/authorization.rb +27 -0
  16. data/lib/tempest_time/api/jira_api/models/issue.rb +28 -0
  17. data/lib/tempest_time/api/jira_api/request.rb +40 -0
  18. data/lib/tempest_time/api/jira_api/requests/get_issue.rb +29 -0
  19. data/lib/tempest_time/api/jira_api/requests/get_user_issues.rb +36 -0
  20. data/lib/tempest_time/api/jira_api/response.rb +5 -0
  21. data/lib/tempest_time/api/jira_api/responses/get_issue.rb +16 -0
  22. data/lib/tempest_time/api/jira_api/responses/get_user_issues.rb +14 -0
  23. data/lib/tempest_time/api/request.rb +87 -0
  24. data/lib/tempest_time/api/response.rb +34 -0
  25. data/lib/tempest_time/api/tempo_api/authorization.rb +23 -0
  26. data/lib/tempest_time/api/tempo_api/models/worklog.rb +36 -0
  27. data/lib/tempest_time/api/tempo_api/request.rb +35 -0
  28. data/lib/tempest_time/api/tempo_api/requests/create_worklog.rb +51 -0
  29. data/lib/tempest_time/api/tempo_api/requests/delete_worklog.rb +29 -0
  30. data/lib/tempest_time/api/tempo_api/requests/get_worklog.rb +29 -0
  31. data/lib/tempest_time/api/tempo_api/requests/list_worklogs.rb +41 -0
  32. data/lib/tempest_time/api/tempo_api/requests/report.rb +0 -0
  33. data/lib/tempest_time/api/tempo_api/requests/submit_timesheet.rb +49 -0
  34. data/lib/tempest_time/api/tempo_api/response.rb +5 -0
  35. data/lib/tempest_time/api/tempo_api/responses/create_worklog.rb +28 -0
  36. data/lib/tempest_time/api/tempo_api/responses/delete_worklog.rb +8 -0
  37. data/lib/tempest_time/api/tempo_api/responses/get_worklog.rb +22 -0
  38. data/lib/tempest_time/api/tempo_api/responses/list_worklogs.rb +40 -0
  39. data/lib/tempest_time/api/tempo_api/responses/submit_timesheet.rb +13 -0
  40. data/lib/tempest_time/cli.rb +92 -0
  41. data/lib/tempest_time/command.rb +60 -0
  42. data/lib/tempest_time/commands/config.rb +30 -0
  43. data/lib/tempest_time/commands/config/edit.rb +38 -0
  44. data/lib/tempest_time/commands/config/setup.rb +55 -0
  45. data/lib/tempest_time/commands/delete.rb +31 -0
  46. data/lib/tempest_time/commands/issues.rb +37 -0
  47. data/lib/tempest_time/commands/list.rb +32 -0
  48. data/lib/tempest_time/commands/report.rb +83 -0
  49. data/lib/tempest_time/commands/submit.rb +26 -0
  50. data/lib/tempest_time/commands/teams.rb +30 -0
  51. data/lib/tempest_time/commands/teams/add.rb +29 -0
  52. data/lib/tempest_time/commands/teams/delete.rb +32 -0
  53. data/lib/tempest_time/commands/teams/edit.rb +49 -0
  54. data/lib/tempest_time/commands/track.rb +70 -0
  55. data/lib/tempest_time/helpers/formatting_helper.rb +16 -0
  56. data/lib/tempest_time/helpers/time_helper.rb +63 -0
  57. data/lib/tempest_time/models/report.rb +58 -0
  58. data/lib/tempest_time/services/generate_report.rb +54 -0
  59. data/lib/tempest_time/setting.rb +47 -0
  60. data/lib/tempest_time/settings/authorization.rb +19 -0
  61. data/lib/tempest_time/settings/teams.rb +19 -0
  62. data/lib/tempest_time/templates/config/.gitkeep +1 -0
  63. data/lib/tempest_time/templates/config/auth/.gitkeep +1 -0
  64. data/lib/tempest_time/templates/config/setup/.gitkeep +1 -0
  65. data/lib/tempest_time/templates/config/teams/.gitkeep +1 -0
  66. data/lib/tempest_time/templates/delete/.gitkeep +1 -0
  67. data/lib/tempest_time/templates/issue/.gitkeep +1 -0
  68. data/lib/tempest_time/templates/issues/.gitkeep +1 -0
  69. data/lib/tempest_time/templates/list/.gitkeep +1 -0
  70. data/lib/tempest_time/templates/report/.gitkeep +1 -0
  71. data/lib/tempest_time/templates/setup/.gitkeep +1 -0
  72. data/lib/tempest_time/templates/submit/.gitkeep +1 -0
  73. data/lib/tempest_time/templates/teams/.gitkeep +1 -0
  74. data/lib/tempest_time/templates/track/.gitkeep +1 -0
  75. data/lib/tempest_time/templates/view/.gitkeep +1 -0
  76. data/lib/tempest_time/version.rb +3 -0
  77. data/tempest-time.gemspec +62 -0
  78. metadata +517 -0
@@ -0,0 +1,5 @@
1
+ require_relative '../response'
2
+
3
+ module TempoAPI
4
+ class Response < TempestTime::API::Response; end
5
+ end
@@ -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,8 @@
1
+ require_relative '../response'
2
+
3
+ module TempoAPI
4
+ module Responses
5
+ class DeleteWorklog < TempoAPI::Response
6
+ end
7
+ end
8
+ 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,13 @@
1
+ require_relative '../response'
2
+
3
+ module TempoAPI
4
+ module Responses
5
+ class SubmitTimesheet < TempoAPI::Response
6
+ private
7
+
8
+ def success_message
9
+ "Timesheet submitted successfully to #{request.reviewer}!"
10
+ end
11
+ end
12
+ end
13
+ 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