tempest_time 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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