time_log_robot 0.1.0 → 0.1.1

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: 02779016739df4a954e1bdb2a1497dec6fc5c75f
4
- data.tar.gz: bb7e9647e14ed514a143ac06f9e3e855388a2446
3
+ metadata.gz: 0559ea642c0065a0807d04c0df40d65b50e6e285
4
+ data.tar.gz: aa9f9fb89b4b9bfa8218fee09d5bfaa6b71b6af0
5
5
  SHA512:
6
- metadata.gz: dfec06ec09c95fef5e4085cbdbc8a8fe4a23efc9a58fa18f1f09b7f01cc0396592c78b00b4c5f1c5b225d1a54906bdaeb91ab31ca32aba5dab4e11a26b6fe916
7
- data.tar.gz: 7fad04cb28781642f91bb496551f3cf38ddf6e5caeb633a22ce7c7d8811ba40823402fb7725e070d9839aa9b8ff0ac686b316e932aa9e884ac16b363d19f99df
6
+ metadata.gz: e71c80181ea7bae7b217a18f9a0c3e03e6218d2b53803b32ab7272b7b22f6de60bc15cdb1d47cbdf8d6f8d243194d10f27bae4855acb1b0cd7594ba2a6f89ba3
7
+ data.tar.gz: 90ece6f79709e99a83cf12040c687cfa3de7839c506e00ba877075674d5be22805bc992d05470bf1a73cf59bc13013d82dab308b4d51d39c8c0645747fe8e473
data/.gitignore CHANGED
@@ -1,2 +1 @@
1
- config/settings.sample.yaml
2
1
  pkg/
data/.settings.yml CHANGED
@@ -1 +0,0 @@
1
- ---
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- time_log_robot (0.1.0)
4
+ time_log_robot (0.1.1)
5
5
  activesupport (~> 4.2, >= 4.2.6)
6
6
  commander (~> 4.1, >= 4.1.6)
7
7
  httparty (~> 0.13, >= 0.13.0)
data/README.md CHANGED
@@ -5,14 +5,8 @@ This is an integration between project management tools (like JIRA) and time log
5
5
 
6
6
  ## Installation
7
7
 
8
- ~~Download this gem:~~ This is not yet on RubyGems, but once it is, you will be able to download like so:
9
-
10
8
  gem install time_log_robot
11
9
 
12
- Until then...
13
-
14
- rake install
15
-
16
10
  ## Usage
17
11
 
18
12
  The simplest usage is just to invoke the robot:
data/bin/time_log_robot CHANGED
@@ -5,6 +5,7 @@ require 'time_log_robot'
5
5
  require 'time_log_robot/version'
6
6
  require 'time_log_robot/toggl/tagger'
7
7
  require 'time_log_robot/toggl/report'
8
+ require 'time_log_robot/jira/issue_key_parser'
8
9
  require 'time_log_robot/jira/payload_builder'
9
10
  require 'time_log_robot/jira/work_logger'
10
11
  require 'yaml'
@@ -8,14 +8,14 @@ module TimeLogRobot
8
8
 
9
9
  def self.start(since)
10
10
  time_entries = fetch_time_entries(since)
11
- JIRA::WorkLogger.new(time_entries: time_entries).log_all
11
+ JIRA::WorkLogger.log_all(time_entries: time_entries)
12
12
  end
13
13
 
14
14
  def self.fetch_time_entries(since)
15
15
  if since.nil?
16
- Toggl::Report.new.fetch
16
+ Toggl::Report.fetch
17
17
  else
18
- Toggl::Report.new.fetch(since: since)
18
+ Toggl::Report.fetch(since: since)
19
19
  end
20
20
  end
21
21
 
@@ -0,0 +1,29 @@
1
+ module TimeLogRobot
2
+ module JIRA
3
+ class IssueKeyParser
4
+ class << self
5
+ def parse(entry)
6
+ matches = entry['description'].match(/(\[(?<issue_key>[^\]]*)\])/)
7
+ if matches.present?
8
+ matches['issue_key']
9
+ else
10
+ get_key_from_key_mapping(entry['description'])
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def get_key_from_key_mapping(description)
17
+ mappings = YAML.load_file(mapping_file_path) || {}
18
+ if found_key = mappings.keys.find { |key| /#{description}/ =~ key }
19
+ mappings[found_key]
20
+ end
21
+ end
22
+
23
+ def mapping_file_path
24
+ File.join(TimeLogRobot.root, 'mapping.yml')
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -1,20 +1,14 @@
1
1
  module TimeLogRobot
2
2
  module JIRA
3
3
  class PayloadBuilder
4
- attr_accessor :start, :duration_in_seconds, :comment
5
-
6
- def initialize(start:, duration_in_seconds:, comment:)
7
- @start = start
8
- @duration_in_seconds = duration_in_seconds
9
- @comment = comment
10
- end
11
-
12
- def build
13
- {
14
- "comment" => comment,
15
- "started" => start.to_s,
16
- "timeSpentSeconds" => duration_in_seconds
17
- }.to_json
4
+ class << self
5
+ def build(start:, duration_in_seconds:, comment:)
6
+ {
7
+ "comment" => comment,
8
+ "started" => start.to_s,
9
+ "timeSpentSeconds" => duration_in_seconds
10
+ }.to_json
11
+ end
18
12
  end
19
13
  end
20
14
  end
@@ -7,93 +7,106 @@ module TimeLogRobot
7
7
 
8
8
  base_uri 'https://hranswerlink.atlassian.net/rest/api/2'
9
9
 
10
- def initialize(time_entries:, log_tags: [ENV['TOGGL_DEFAULT_LOG_TAG']])
11
- @username = ENV['JIRA_USERNAME']
12
- @password = ENV['JIRA_PASSWORD']
13
- @time_entries = time_entries
14
- @log_tags = log_tags
15
- end
10
+ class << self
11
+ def log_all(time_entries:)
12
+ time_entries.each do |entry|
13
+ log(entry) unless is_logged?(entry)
14
+ end
15
+ end
16
16
 
17
- def log_all
18
- time_entries.each do |entry|
19
- log(entry) unless is_logged?(entry)
17
+ private
18
+
19
+ def username
20
+ ENV['JIRA_USERNAME']
20
21
  end
21
- end
22
22
 
23
- private
23
+ def password
24
+ ENV['JIRA_PASSWORD']
25
+ end
24
26
 
25
- def is_logged?(entry)
26
- (log_tags - entry['tags']).size < log_tags.size
27
- end
27
+ def log_tags
28
+ [ENV['TOGGL_DEFAULT_LOG_TAG']]
29
+ end
28
30
 
29
- def log(entry)
30
- issue_key = parse_issue_key(entry)
31
- payload = build_payload(entry)
32
- puts "Attempting to log #{human_readable_duration(parse_duration(entry))}"
33
- puts "starting on #{parse_start(entry)}"
34
- puts "to #{entry['description']}"
35
- puts "with comment #{parse_comment(entry)}" unless parse_comment(entry).nil?
36
- response = self.class.post("/issue/#{issue_key}/worklog", basic_auth: auth, headers: headers, body: payload)
37
- if response.success?
38
- puts "Success"
39
- puts '*' * 20
40
- set_entry_as_logged(entry)
41
- else
42
- puts "Failed! Response from JIRA:"
43
- puts response
44
- puts "(Hint: Did you forget to put the JIRA issue key in your Toggl entry?"
45
- puts '*' * 20
31
+ def is_logged?(entry)
32
+ (log_tags - entry['tags']).size < log_tags.size
46
33
  end
47
- end
48
34
 
49
- def set_entry_as_logged(entry)
50
- Toggl::Tagger.new(tags: log_tags).update(entry_id: entry['id'])
51
- end
35
+ def log(entry)
36
+ issue_key = JIRA::IssueKeyParser.parse(entry)
37
+ payload = build_payload(entry)
38
+ puts "Attempting to log #{human_readable_duration(parse_duration(entry))}"
39
+ puts "starting on #{parse_start(entry)}"
40
+ puts "to #{entry['description']}"
41
+ puts "issue key #{issue_key}"
42
+ puts "with comment #{parse_comment(entry)}" unless parse_comment(entry).nil?
43
+ response = post("/issue/#{issue_key}/worklog", basic_auth: auth, headers: headers, body: payload)
44
+ if response.success?
45
+ puts "Success"
46
+ puts '*' * 20
47
+ set_entry_as_logged(entry)
48
+ else
49
+ puts response.code
50
+ puts "Failed! Response from JIRA:"
51
+ if response.code == 401
52
+ raise UnauthorizedError, "Please check your username and password and try again"
53
+ elsif response.code == 404
54
+ puts "Not Found - Did you forget to put the JIRA issue key in your Toggl entry?"
55
+ end
56
+ puts '*' * 20
57
+ end
58
+ end
59
+ class UnauthorizedError < Exception; end
52
60
 
53
- def auth
54
- {
55
- username: username,
56
- password: password
57
- }
58
- end
61
+ def set_entry_as_logged(entry)
62
+ Toggl::Tagger.update(entry_id: entry['id'])
63
+ end
59
64
 
60
- # @TODO Extract since it's used in several different models
61
- def headers
62
- { 'Content-Type' => 'application/json' }
63
- end
65
+ def auth
66
+ {
67
+ username: username,
68
+ password: password
69
+ }
70
+ end
64
71
 
65
- def build_payload(entry)
66
- JIRA::PayloadBuilder.new(
67
- start: parse_start(entry),
68
- duration_in_seconds: parse_duration(entry),
69
- comment: parse_comment(entry)
70
- ).build
71
- end
72
+ # @TODO Extract since it's used in several different models
73
+ def headers
74
+ { 'Content-Type' => 'application/json' }
75
+ end
72
76
 
73
- def parse_start(entry)
74
- DateTime.strptime(entry['start'], "%FT%T%:z").strftime("%FT%T.%L%z")
75
- end
77
+ def build_payload(entry)
78
+ JIRA::PayloadBuilder.build(
79
+ start: parse_start(entry),
80
+ duration_in_seconds: parse_duration(entry),
81
+ comment: parse_comment(entry)
82
+ )
83
+ end
76
84
 
77
- def parse_duration(entry)
78
- entry['dur']/1000 # Toggl sends times in milliseconds
79
- end
85
+ def parse_start(entry)
86
+ DateTime.strptime(entry['start'], "%FT%T%:z").strftime("%FT%T.%L%z")
87
+ end
80
88
 
81
- def human_readable_duration(seconds)
82
- total_minutes = seconds/60
83
- hours = total_minutes/60
84
- remaining_minutes = total_minutes - hours * 60
85
- "#{hours}h #{remaining_minutes}m"
86
- end
89
+ def parse_duration(entry)
90
+ entry['dur']/1000 # Toggl sends times in milliseconds
91
+ end
87
92
 
88
- # @TODO figure out how to capture both of this in one .match call with one set of regex
89
- def parse_issue_key(entry)
90
- matches = entry['description'].match(/(\[(?<issue_key>[^\]]*)\])/)
91
- matches['issue_key'] if matches.present?
92
- end
93
+ def human_readable_duration(seconds)
94
+ total_minutes = seconds/60
95
+ hours = total_minutes/60
96
+ remaining_minutes = total_minutes - hours * 60
97
+ "#{hours}h #{remaining_minutes}m"
98
+ end
93
99
 
94
- def parse_comment(entry)
95
- matches = entry['description'].match(/(\{(?<comment>[^\}]*)\})/)
96
- matches['comment'] if matches.present?
100
+ # @TODO figure out how to capture both of this in one .match call with one set of regex
101
+ def parse_issue_key(entry)
102
+ matches = entry['description'].match(/(\[(?<issue_key>[^\]]*)\])/)
103
+ matches['issue_key'] if matches.present?
104
+ end
105
+
106
+ def parse_comment(entry)
107
+ matches = entry['description'].match(/(\{(?<comment>[^\}]*)\})/)
108
+ matches['comment'] if matches.present?
109
+ end
97
110
  end
98
111
  end
99
112
  end
@@ -5,44 +5,49 @@ module TimeLogRobot
5
5
  class Report
6
6
  include HTTParty
7
7
 
8
- attr_accessor :token, :workspace_id, :user_agent
9
-
10
8
  base_uri 'https://toggl.com/reports/api/v2'
11
9
 
12
- def initialize
13
- @token = ENV['TOGGL_TOKEN']
14
- @workspace_id = ENV['TOGGL_WORKSPACE_ID']
15
- @user_agent = ENV['TOGGL_USER_AGENT']
16
- end
10
+ class << self
11
+ def fetch(since: nil)
12
+ since ||= Date.today.beginning_of_week(:saturday).to_time
13
+ response = get('/details', basic_auth: auth, query: query(since))
14
+ if response.success?
15
+ response['data']
16
+ else
17
+ raise FetchError, response['error']
18
+ end
19
+ end
20
+ class FetchError < Exception; end
17
21
 
18
- def fetch(since: nil)
19
- since ||= Date.today.beginning_of_week(:saturday).to_time
20
- response = self.class.get('/details', basic_auth: auth, query: query(since))
21
- if response.success?
22
- response['data']
23
- else
24
- raise FetchError, response['error']
22
+ private
23
+
24
+ def auth
25
+ {
26
+ username: token,
27
+ password: "api_token"
28
+ }
29
+ end
30
+
31
+ def token
32
+ ENV['TOGGL_TOKEN']
25
33
  end
26
- end
27
- class FetchError < Exception; end
28
34
 
29
- private
35
+ def workspace_id
36
+ ENV['TOGGL_WORKSPACE_ID']
37
+ end
30
38
 
31
- def auth
32
- {
33
- username: token,
34
- password: "api_token"
35
- }
36
- end
39
+ def user_agent
40
+ ENV['TOGGL_USER_AGENT']
41
+ end
37
42
 
38
- def query(since)
39
- {
40
- workspace_id: workspace_id,
41
- user_agent: user_agent,
42
- since: since
43
- }
43
+ def query(since)
44
+ {
45
+ workspace_id: workspace_id,
46
+ user_agent: user_agent,
47
+ since: since
48
+ }
49
+ end
44
50
  end
45
51
  end
46
-
47
52
  end
48
53
  end
@@ -3,39 +3,42 @@ module TimeLogRobot
3
3
  class Tagger
4
4
  include HTTParty
5
5
 
6
- attr_accessor :token, :tags
7
-
8
6
  base_uri 'https://toggl.com/api/v8/time_entries'
9
7
 
10
- def initialize(tags:[ENV['TOGGL_DEFAULT_LOG_TAG']])
11
- @token = ENV['TOGGL_TOKEN']
12
- @tags = tags
13
- end
8
+ class << self
9
+ def update(entry_id:)
10
+ put("/#{entry_id}", basic_auth: auth, headers: headers, body: body)
11
+ end
14
12
 
15
- def update(entry_id:)
16
- self.class.put("/#{entry_id}", basic_auth: auth, headers: headers, body: body)
17
- end
13
+ private
18
14
 
19
- private
15
+ def auth
16
+ {
17
+ username: token,
18
+ password: "api_token"
19
+ }
20
+ end
20
21
 
21
- def auth
22
- {
23
- username: token,
24
- password: "api_token"
25
- }
26
- end
22
+ def token
23
+ ENV['TOGGL_TOKEN']
24
+ end
27
25
 
28
- def headers
29
- { 'Content-Type' => 'application/json' }
30
- end
26
+ def headers
27
+ { 'Content-Type' => 'application/json' }
28
+ end
31
29
 
32
- def body
33
- {
34
- time_entry:
30
+ def body
35
31
  {
36
- tags: tags
37
- }
38
- }.to_json
32
+ time_entry:
33
+ {
34
+ tags: tags
35
+ }
36
+ }.to_json
37
+ end
38
+
39
+ def tags
40
+ [ENV['TOGGL_DEFAULT_LOG_TAG']]
41
+ end
39
42
  end
40
43
  end
41
44
  end
@@ -1,3 +1,3 @@
1
1
  module TimeLogRobot
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
data/mapping.yml ADDED
@@ -0,0 +1,3 @@
1
+ monday meeting: PM-1
2
+ weekly planning meeting: PM-1
3
+ time log robot: OSS-5
@@ -1,12 +1,11 @@
1
1
  # coding: utf-8
2
2
  lib = File.expand_path('../lib', __FILE__)
3
3
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- # require 'time_log_robot/version'
4
+ require 'time_log_robot/version'
5
5
 
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'time_log_robot'
8
- spec.version = '0.1.0'
9
- # spec.version = TimeLogRobot::VERSION
8
+ spec.version = TimeLogRobot::VERSION
10
9
  spec.authors = ['Mark J. Lehman']
11
10
  spec.email = ['markopolo@gmail.com']
12
11
  spec.description = %q{Automate time logging from tools like Toggl to project management software such as JIRA}
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: time_log_robot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark J. Lehman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-05-19 00:00:00.000000000 Z
11
+ date: 2016-05-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -136,11 +136,13 @@ files:
136
136
  - Rakefile
137
137
  - bin/time_log_robot
138
138
  - lib/time_log_robot.rb
139
+ - lib/time_log_robot/jira/issue_key_parser.rb
139
140
  - lib/time_log_robot/jira/payload_builder.rb
140
141
  - lib/time_log_robot/jira/work_logger.rb
141
142
  - lib/time_log_robot/toggl/report.rb
142
143
  - lib/time_log_robot/toggl/tagger.rb
143
144
  - lib/time_log_robot/version.rb
145
+ - mapping.yml
144
146
  - time_log_robot.gemspec
145
147
  homepage: https://github.com/supremebeing7/time_log_robot
146
148
  licenses: