tempest_time 1.1.1 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 35061a30f433d5e3bd350345cebd9e360979284f
4
- data.tar.gz: a9bd9bef56747b2b69a9966b2b0a2220c3ce8952
3
+ metadata.gz: a0741b1d3f1af0e3145fbcb9039d3856135c455d
4
+ data.tar.gz: f8c88a25127cfb070a31b6ad76da191306e49faf
5
5
  SHA512:
6
- metadata.gz: 3d11c9b88e5d80415830c6d4bd919dd745a4e9558d0f0284d85913a0e3d429a13d57ac2e8fe5ca1f4301fc017f4fded2abaf31bd4df0b862803ee739faac7d8f
7
- data.tar.gz: c988aa0a5a2db5423fe73932047b676afc95aea7a25e49efca5bcb982223f9193d3a492a5bb9e8c268bcd559f042c94f5de5a6322996090e6cce63febc61ea40
6
+ metadata.gz: 6243e1801cdcc99e376cc1a57ad36022c3ef7cd2077adbb584c5a6c3d505308523ad6e63fa2f4813d9aec99fe71261957834b1ac5a8fb914a6f6966a7b8ccd8e
7
+ data.tar.gz: f1fa740661e844fca870aa50848560e93eb5ec309a8ccaa9380ee94f5af4ef7f1e4180970958c52341f62d014b4f4be1bd2fbbf40003f9d887236bbc18f79e83
@@ -0,0 +1,40 @@
1
+ require_relative '../request'
2
+ require_relative '../responses/get_user_schedule'
3
+
4
+ module TempoAPI
5
+ module Requests
6
+ class GetUserSchedule < TempoAPI::Request
7
+ attr_reader :start_date, :end_date
8
+
9
+ def initialize(start_date:, end_date:, requested_user:)
10
+ super
11
+ @start_date = start_date
12
+ @end_date = end_date || start_date
13
+ @requested_user = requested_user || user
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :requested_user
19
+
20
+ def request_method
21
+ 'get'
22
+ end
23
+
24
+ def request_path
25
+ "/user-schedule/#{requested_user}"
26
+ end
27
+
28
+ def query_params
29
+ {
30
+ "from": start_date.strftime(DATE_FORMAT),
31
+ "to": end_date.strftime(DATE_FORMAT),
32
+ }
33
+ end
34
+
35
+ def response_klass
36
+ TempoAPI::Responses::GetUserSchedule
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../response'
4
+ require_relative '../../../helpers/time_helper'
5
+
6
+ module TempoAPI
7
+ module Responses
8
+ class GetUserSchedule < TempoAPI::Response
9
+ include TempestTime::Helpers::TimeHelper
10
+
11
+ def required_seconds
12
+ results.map { |result| result['requiredSeconds'] }.reduce(:+)
13
+ end
14
+
15
+ private
16
+
17
+ def results
18
+ @results = raw_response['results'] || []
19
+ end
20
+ end
21
+ end
22
+ end
@@ -31,6 +31,12 @@ module TempestTime
31
31
  'issue', 'issue [SUBCOMMAND]',
32
32
  'View and modify Jira issues.'
33
33
 
34
+ desc 'setup', 'Perform initial Tempest setup.'
35
+ def setup(*)
36
+ require_relative 'commands/setup'
37
+ TempestTime::Commands::Setup.new(options).execute
38
+ end
39
+
34
40
  desc 'list', 'List worklogs for a specific date.'
35
41
  option :user, aliases: '-u', type: :string
36
42
  option :date, aliases: '-d', type: :string
@@ -7,16 +7,16 @@ module TempestTime
7
7
  class Config < Thor
8
8
  namespace :config
9
9
 
10
- desc 'setup', 'Set up Tempest with your credentials.'
11
- def setup(*)
12
- require_relative 'config/setup'
13
- TempestTime::Commands::Config::Setup.new(options).execute
10
+ desc 'edit', 'Modify your jira/tempo authentication settings.'
11
+ def auth(*)
12
+ require_relative 'config/auth'
13
+ TempestTime::Commands::Config::Auth.new(options).execute
14
14
  end
15
15
 
16
- desc 'edit', 'Modify your user credentials.'
17
- def edit(*)
18
- require_relative 'config/edit'
19
- TempestTime::Commands::Config::Edit.new(options).execute
16
+ desc 'app', 'Modify your app settings.'
17
+ def app(*)
18
+ require_relative 'config/app'
19
+ TempestTime::Commands::Config::App.new(options).execute
20
20
  end
21
21
  end
22
22
  end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../command'
4
+ require_relative '../../settings/app'
5
+
6
+ module TempestTime
7
+ module Commands
8
+ class Config
9
+ class App < TempestTime::Command
10
+ def initialize(options)
11
+ @options = options
12
+ end
13
+
14
+ def execute!
15
+ settings = TempestTime::Settings::App.new
16
+ setting = prompt.select(
17
+ "Which #{pastel.green('setting')} would you like to edit?",
18
+ available_options
19
+ )
20
+
21
+ current = settings.fetch(setting)
22
+ new_value = send(setting, current)
23
+
24
+ if new_value != settings.fetch(setting)
25
+ settings.set(setting, new_value)
26
+ prompt.say(pastel.green('Setting updated!'))
27
+ else
28
+ prompt.say('Nothing changed.')
29
+ end
30
+
31
+ execute if prompt.yes?('Keep editing?')
32
+ end
33
+
34
+ private
35
+
36
+ def available_options
37
+ [
38
+ 'internal_projects',
39
+ ]
40
+ end
41
+
42
+ def internal_projects(current)
43
+ message =
44
+ 'Please enter the ' + pastel.green('project codes') + ' of internal projects. '\
45
+ 'This is used to calculate utilization percentage in reporting. (Comma-separated)'
46
+ prompt.ask(message, default: current&.join(', ')) do |q|
47
+ q.convert ->(input) { input.split(/,\s*/) }
48
+ end.sort
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -6,7 +6,7 @@ require_relative '../../settings/authorization'
6
6
  module TempestTime
7
7
  module Commands
8
8
  class Config
9
- class Edit < TempestTime::Command
9
+ class Auth < TempestTime::Command
10
10
  def initialize(options)
11
11
  @options = options
12
12
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../command'
4
+ require_relative '../settings/app'
4
5
  require_relative '../settings/teams'
5
6
  require_relative '../api/tempo_api/requests/list_worklogs'
7
+ require_relative '../api/tempo_api/requests/get_user_schedule'
6
8
  require_relative '../models/report'
7
9
 
8
10
  module TempestTime
@@ -32,6 +34,7 @@ module TempestTime
32
34
  "#{formatted_date(end_date)}"
33
35
  prompt.say("\nReport for #{pastel.green(date_range)}")
34
36
  puts table
37
+ display_warnings
35
38
  end
36
39
  end
37
40
 
@@ -69,34 +72,54 @@ module TempestTime
69
72
  @end_date ||= week_dates(@week).last
70
73
  end
71
74
 
75
+ def required_seconds
76
+ @required_seconds ||= TempoAPI::Requests::GetUserSchedule.new(
77
+ start_date: start_date,
78
+ end_date: end_date,
79
+ requested_user: @users.first
80
+ ).send_request.required_seconds
81
+ end
82
+
72
83
  def reports
73
84
  @reports ||= @users.map do |user|
74
- list = TempoAPI::Requests::ListWorklogs.new(
85
+ worklogs = TempoAPI::Requests::ListWorklogs.new(
75
86
  start_date,
76
87
  end_date: end_date,
77
88
  requested_user: user
78
- ).send_request
79
- TempestTime::Models::Report.new(user, list.worklogs)
89
+ ).send_request.worklogs
90
+
91
+ TempestTime::Models::Report.new(
92
+ user: user,
93
+ required_seconds: required_seconds,
94
+ worklogs: worklogs,
95
+ internal_projects: internal_projects
96
+ )
80
97
  end || []
81
98
  end
82
99
 
83
100
  def aggregate
84
101
  @aggregate ||= TempestTime::Models::Report.new(
85
- 'TOTAL',
86
- reports.flat_map(&:worklogs),
87
- number_of_users: @users.count
102
+ user: 'TOTAL',
103
+ worklogs: reports.flat_map(&:worklogs),
104
+ required_seconds: required_seconds,
105
+ number_of_users: @users.count,
106
+ internal_projects: internal_projects
88
107
  )
89
108
  end
90
109
 
91
110
  def projects
92
111
  @projects ||= reports.flat_map(&:projects).uniq.sort.tap do |projects|
93
- projects.delete('BCIT')
94
- projects.unshift('BCIT')
112
+ # Put internal project codes at the front of the array.
113
+ internal_projects.reverse.each do |code|
114
+ next unless projects.include?(code)
115
+ projects.delete(code)
116
+ projects.unshift(code)
117
+ end
95
118
  end
96
119
  end
97
120
 
98
121
  def table_headings
99
- %w(User COMP% UTIL%) + projects
122
+ %w(User COMP% UTIL%) + projects + ['TOTAL HOURS']
100
123
  end
101
124
 
102
125
  def render_table
@@ -123,6 +146,12 @@ module TempestTime
123
146
  )
124
147
  )
125
148
  end
149
+ row.push(
150
+ right_align(
151
+ formatted_time_for_input(data.total_seconds_logged).to_s +
152
+ ' / ' + formatted_time_for_input(data.required_seconds).to_s
153
+ )
154
+ )
126
155
  row
127
156
  end
128
157
 
@@ -134,6 +163,22 @@ module TempestTime
134
163
  return '' unless decimal.positive?
135
164
  (decimal * 100.0).round(1).to_s + '%'
136
165
  end
166
+
167
+ def internal_projects
168
+ @internal_projects ||= TempestTime::Settings::App.new.fetch('internal_projects') || []
169
+ end
170
+
171
+ def display_warnings
172
+ if internal_projects.empty?
173
+ prompt.say(
174
+ pastel.yellow(
175
+ 'You have not set any internal projects. '\
176
+ 'To calculate utilization percentage correctly, please add an internal project. '\
177
+ '(`tempest config app`)'
178
+ )
179
+ )
180
+ end
181
+ end
137
182
  end
138
183
  end
139
184
  end
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../command'
4
+ require_relative '../settings/authorization'
5
+ require_relative '../settings/app'
6
+ require_relative '../api/jira_api/requests/get_current_user'
7
+ require_relative '../api/tempo_api/requests/list_worklogs'
8
+
9
+ module TempestTime
10
+ module Commands
11
+ class Setup < TempestTime::Command
12
+ def initialize(options)
13
+ @options = options
14
+ end
15
+
16
+ def execute!
17
+ check_for_completion
18
+ set_authorization_values
19
+ set_app_settings
20
+ with_spinner('Checking credentials...') do |spinner|
21
+ begin
22
+ check_for_validity
23
+ spinner.stop(pastel.green("Success! You're ready to go!"))
24
+ rescue StandardError
25
+ spinner.stop(
26
+ pastel.red("Something isn't right... please re-run setup.")
27
+ )
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def credentials
35
+ @credentials ||= {
36
+ 'email' => email,
37
+ 'username' => username,
38
+ 'subdomain' => subdomain,
39
+ 'jira_token' => jira_token,
40
+ 'tempo_token' => tempo_token,
41
+ }
42
+ end
43
+
44
+ def app_settings_values
45
+ @app_settings_values ||= {
46
+ 'internal_projects' => internal_projects,
47
+ }
48
+ end
49
+
50
+ def authorization
51
+ @authorization ||= TempestTime::Settings::Authorization.new
52
+ end
53
+
54
+ def app_settings
55
+ @app_settings ||= TempestTime::Settings::App.new
56
+ end
57
+
58
+ def check_for_completion
59
+ return true unless credentials.any? { |_, value| value.nil? }
60
+ abort(
61
+ pastel.red('Setup failed!') + ' ' \
62
+ 'One or more credentials were missing. Please try again.'
63
+ )
64
+ end
65
+
66
+ def set_authorization_values
67
+ credentials.map { |key, value| authorization.set(key, value) }
68
+ end
69
+
70
+ def set_app_settings
71
+ app_settings_values.map { |key, value| app_settings.set(key, value) }
72
+ end
73
+
74
+ def check_for_validity
75
+ jira = JiraAPI::Requests::GetCurrentUser.new
76
+ tempo = TempoAPI::Requests::ListWorklogs.new(Date.today)
77
+ raise StandardError unless jira.send_request.success?
78
+ raise StandardError unless tempo.send_request.success?
79
+ end
80
+
81
+ def email
82
+ prompt.ask(
83
+ 'Please enter your Atlassian ID. '\
84
+ 'This is typically your login email.',
85
+ default: authorization.fetch('email')
86
+ )
87
+ end
88
+
89
+ def username
90
+ prompt.ask(
91
+ 'Please enter your Atlassian username.',
92
+ default: authorization.fetch('username')
93
+ )
94
+ end
95
+
96
+ def subdomain
97
+ prompt.ask(
98
+ 'Please enter your Atlassian subdomain. '\
99
+ 'i.e. [THIS VALUE].atlassian.net',
100
+ default: authorization.fetch('subdomain')
101
+ )
102
+ end
103
+
104
+ def jira_token
105
+ prompt.ask(
106
+ 'Please enter your Atlassian API token. '\
107
+ 'Your token can be generated at '\
108
+ 'https://id.atlassian.com/manage/api-tokens',
109
+ default: authorization.fetch('jira_token')
110
+ )
111
+ end
112
+
113
+ def tempo_token
114
+ prompt.ask(
115
+ 'Please enter your Tempo API token. '\
116
+ 'Your token can be generated through '\
117
+ "your worksheet's settings page.",
118
+ default: authorization.fetch('tempo_token')
119
+ )
120
+ end
121
+
122
+ def internal_projects
123
+ current = app_settings.fetch('internal_projects')&.join(', ') || ''
124
+
125
+ prompt.ask(
126
+ 'Please enter the project codes of internal projects. (Optional, comma-separated.) '\
127
+ 'This is used to calculate utilization percentage in reporting.',
128
+ default: current
129
+ ) do |q|
130
+ q.convert ->(input) { input.split(/,\s*/) }
131
+ end.sort
132
+ end
133
+ end
134
+ end
135
+ end
@@ -4,15 +4,14 @@ module TempestTime
4
4
  module Models
5
5
  class Report
6
6
  include TempestTime::Helpers::FormattingHelper
7
- EXPECTED_SECONDS = (37.5 * 60 * 60).to_i.freeze
8
- INTERNAL_PROJECT = 'BCIT'.freeze
7
+ attr_reader :user, :worklogs, :number_of_users, :required_seconds, :internal_projects
9
8
 
10
- attr_reader :user, :worklogs, :number_of_users
11
-
12
- def initialize(user, worklogs, number_of_users: 1)
9
+ def initialize(user:, worklogs:, required_seconds:, internal_projects:, number_of_users: 1)
13
10
  @user = user
14
11
  @worklogs = worklogs
15
12
  @number_of_users = number_of_users
13
+ @required_seconds = required_seconds * number_of_users
14
+ @internal_projects = internal_projects
16
15
  end
17
16
 
18
17
  def project_total_times
@@ -22,7 +21,7 @@ module TempestTime
22
21
  end
23
22
 
24
23
  def compliance_percentage(time)
25
- (time.to_f / (EXPECTED_SECONDS * number_of_users))
24
+ (time.to_f / (required_seconds * number_of_users))
26
25
  end
27
26
 
28
27
  def project_compliance_percentages
@@ -38,11 +37,15 @@ module TempestTime
38
37
  def utilization_percentage
39
38
  @utilization_percentage ||=
40
39
  project_compliance_percentages.inject(0) do |memo, (project, percentage)|
41
- memo += percentage unless project == INTERNAL_PROJECT
40
+ memo += percentage unless internal_projects.include?(project)
42
41
  memo
43
42
  end
44
43
  end
45
44
 
45
+ def total_seconds_logged
46
+ @total_seconds_logged ||= time_logged_seconds(worklogs)
47
+ end
48
+
46
49
  def time_logged_seconds(logs)
47
50
  logs.flat_map(&:seconds).reduce(:+)
48
51
  end
@@ -44,6 +44,8 @@ module TempestTime
44
44
  def read_config
45
45
  config.read
46
46
  yield
47
+ rescue TTY::Config::ReadError
48
+ nil
47
49
  end
48
50
 
49
51
  def write_config
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TempestTime
4
- VERSION = '1.1.1'
4
+ VERSION = '1.2.0'
5
5
  end
data/tempest_time.gemspec CHANGED
@@ -14,6 +14,15 @@ Gem::Specification.new do |spec|
14
14
  spec.description = %q(Log time and more... directly from the command line!)
15
15
  spec.homepage = 'https://github.com/devanhurst/tempest_time'
16
16
 
17
+ spec.post_install_message = %q(
18
+ Thank you for installing Tempest!
19
+
20
+ If this is your first time installing, please run `tempest setup` to provide your Atlassian credentials.
21
+
22
+ UPDATE: v1.2.0 makes changes to how reports are calculated!
23
+ If you have been using this feature, please run `tempest config app` to set up your internal project codes.
24
+ )
25
+
17
26
  spec.required_ruby_version = '~> 2.3'
18
27
 
19
28
  spec.files = `git ls-files -z`.split("\x0").reject do |f|
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: 1.1.1
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Devan Hurst
@@ -262,26 +262,29 @@ files:
262
262
  - lib/tempest_time/api/tempo_api/request.rb
263
263
  - lib/tempest_time/api/tempo_api/requests/create_worklog.rb
264
264
  - lib/tempest_time/api/tempo_api/requests/delete_worklog.rb
265
+ - lib/tempest_time/api/tempo_api/requests/get_user_schedule.rb
265
266
  - lib/tempest_time/api/tempo_api/requests/get_worklog.rb
266
267
  - lib/tempest_time/api/tempo_api/requests/list_worklogs.rb
267
268
  - lib/tempest_time/api/tempo_api/requests/submit_timesheet.rb
268
269
  - lib/tempest_time/api/tempo_api/response.rb
269
270
  - lib/tempest_time/api/tempo_api/responses/create_worklog.rb
270
271
  - lib/tempest_time/api/tempo_api/responses/delete_worklog.rb
272
+ - lib/tempest_time/api/tempo_api/responses/get_user_schedule.rb
271
273
  - lib/tempest_time/api/tempo_api/responses/get_worklog.rb
272
274
  - lib/tempest_time/api/tempo_api/responses/list_worklogs.rb
273
275
  - lib/tempest_time/api/tempo_api/responses/submit_timesheet.rb
274
276
  - lib/tempest_time/cli.rb
275
277
  - lib/tempest_time/command.rb
276
278
  - lib/tempest_time/commands/config.rb
277
- - lib/tempest_time/commands/config/edit.rb
278
- - lib/tempest_time/commands/config/setup.rb
279
+ - lib/tempest_time/commands/config/app.rb
280
+ - lib/tempest_time/commands/config/auth.rb
279
281
  - lib/tempest_time/commands/delete.rb
280
282
  - lib/tempest_time/commands/issue.rb
281
283
  - lib/tempest_time/commands/issue/list.rb
282
284
  - lib/tempest_time/commands/issue/open.rb
283
285
  - lib/tempest_time/commands/list.rb
284
286
  - lib/tempest_time/commands/report.rb
287
+ - lib/tempest_time/commands/setup.rb
285
288
  - lib/tempest_time/commands/submit.rb
286
289
  - lib/tempest_time/commands/teams.rb
287
290
  - lib/tempest_time/commands/teams/add.rb
@@ -324,7 +327,11 @@ homepage: https://github.com/devanhurst/tempest_time
324
327
  licenses:
325
328
  - MIT
326
329
  metadata: {}
327
- post_install_message:
330
+ post_install_message: "\n Thank you for installing Tempest!\n\n If this is your
331
+ first time installing, please run `tempest setup` to provide your Atlassian credentials.\n\n
332
+ \ UPDATE: v1.2.0 makes changes to how reports are calculated!\n If you have
333
+ been using this feature, please run `tempest config app` to set up your internal
334
+ project codes.\n "
328
335
  rdoc_options: []
329
336
  require_paths:
330
337
  - lib
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../../command'
4
- require_relative '../../settings/authorization'
5
- require_relative '../../api/jira_api/requests/get_current_user'
6
- require_relative '../../api/tempo_api/requests/list_worklogs'
7
-
8
- module TempestTime
9
- module Commands
10
- class Config
11
- class Setup < TempestTime::Command
12
- def initialize(options)
13
- @options = options
14
- end
15
-
16
- def execute!
17
- check_for_completion
18
- set_authorization_values
19
- with_spinner('Checking credentials...') do |spinner|
20
- begin
21
- check_for_validity
22
- spinner.stop(pastel.green("Success! You're ready to go!"))
23
- rescue StandardError
24
- spinner.stop(
25
- pastel.red("Something isn't right... please re-run setup.")
26
- )
27
- end
28
- end
29
- end
30
-
31
- private
32
-
33
- def credentials
34
- @credentials ||= {
35
- 'email' => email,
36
- 'username' => username,
37
- 'subdomain' => subdomain,
38
- 'jira_token' => jira_token,
39
- 'tempo_token' => tempo_token,
40
- }
41
- end
42
-
43
- def authorization
44
- @authorization ||= TempestTime::Settings::Authorization.new
45
- end
46
-
47
- def check_for_completion
48
- return true unless credentials.any? { |_, value| value.nil? }
49
- abort(
50
- pastel.red('Setup failed!') + ' ' \
51
- 'One or more credentials were missing. Please try again.'
52
- )
53
- end
54
-
55
- def set_authorization_values
56
- credentials.map { |key, value| authorization.set(key, value) }
57
- end
58
-
59
- def check_for_validity
60
- jira = JiraAPI::Requests::GetCurrentUser.new
61
- tempo = TempoAPI::Requests::ListWorklogs.new(Date.today)
62
- raise StandardError unless jira.send_request.success?
63
- raise StandardError unless tempo.send_request.success?
64
- end
65
-
66
- def email
67
- prompt.ask(
68
- 'Please enter your Atlassian ID. '\
69
- 'This is typically your login email.'
70
- )
71
- end
72
-
73
- def username
74
- prompt.ask('Please enter your Atlassian username.')
75
- end
76
-
77
- def subdomain
78
- prompt.ask(
79
- 'Please enter your Atlassian subdomain. '\
80
- 'i.e. [THIS VALUE].atlassian.net'
81
- )
82
- end
83
-
84
- def jira_token
85
- prompt.ask(
86
- 'Please enter your Atlassian API token. '\
87
- 'Your token can be generated at '\
88
- 'https://id.atlassian.com/manage/api-tokens'
89
- )
90
- end
91
-
92
- def tempo_token
93
- prompt.ask(
94
- 'Please enter your Tempo API token. '\
95
- 'Your token can be generated through '\
96
- "your worksheet's settings page."
97
- )
98
- end
99
- end
100
- end
101
- end
102
- end