time_log_robot 0.1.0 → 0.1.1

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: 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: