timetrap-harvest 1.0.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 +7 -0
- data/lib/timetrap-harvest.rb +42 -0
- data/lib/timetrap_harvest/config.rb +62 -0
- data/lib/timetrap_harvest/formatter.rb +57 -0
- data/lib/timetrap_harvest/harvester.rb +30 -0
- data/lib/timetrap_harvest/network_client.rb +29 -0
- data/lib/timetrap_harvest/output.rb +51 -0
- data/lib/timetrap_harvest/version.rb +3 -0
- metadata +113 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 03fab5bae6ce25a24e1981f32c5922b288873441
|
|
4
|
+
data.tar.gz: d8a7d3b7ab8afd04e425b2563ed6b5e34155e126
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d99ff0d276e539a1b7912ed936caad6e0f404161fe9ec945e5475ea195b28fba0fd860954b74588e2f26f32d53920f6d3576fc0b9378515c5131a80e13c0a5fd
|
|
7
|
+
data.tar.gz: 951ccb521d6f69789ce7a94ffb72b86ac4d23a30b8c516a267e8b590ebd020df8a9a34eae4499da311c22d7a46b582ef568b1c5d8bfcb662b1b7b9aa635c2194
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
require_relative './timetrap_harvest/config'
|
|
2
|
+
require_relative './timetrap_harvest/network_client'
|
|
3
|
+
require_relative './timetrap_harvest/formatter'
|
|
4
|
+
require_relative './timetrap_harvest/harvester'
|
|
5
|
+
require_relative './timetrap_harvest/output'
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
Module.const_get('Timetrap')
|
|
9
|
+
rescue NameError
|
|
10
|
+
module Timetrap;
|
|
11
|
+
module Formatters; end
|
|
12
|
+
Config = { 'harvest' => { 'aliases' => {} } }
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class Timetrap::Formatters::Harvest
|
|
17
|
+
attr_reader :entries
|
|
18
|
+
attr_writer :client, :config
|
|
19
|
+
|
|
20
|
+
def initialize(entries)
|
|
21
|
+
@entries = entries
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def output
|
|
25
|
+
results = entries.map { |entry| TimetrapHarvest::Formatter.new(entry, config).format }
|
|
26
|
+
|
|
27
|
+
harvester = TimetrapHarvest::Harvester.new(results, client)
|
|
28
|
+
results = harvester.harvest
|
|
29
|
+
|
|
30
|
+
TimetrapHarvest::Output.new(results).generate
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def config
|
|
36
|
+
@config ||= TimetrapHarvest::Config.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def client
|
|
40
|
+
@client ||= TimetrapHarvest::NetworkClient.new(config)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
class TimetrapHarvest::Config
|
|
2
|
+
MissingHarvestConfig = Class.new(StandardError)
|
|
3
|
+
MissingHarvestAliases = Class.new(StandardError)
|
|
4
|
+
MissingHarvestSubdomain = Class.new(StandardError)
|
|
5
|
+
DEFAULT_ROUND_IN_MINUTES = 15
|
|
6
|
+
|
|
7
|
+
attr_reader :timetrap_config
|
|
8
|
+
|
|
9
|
+
def initialize(timetrap_config = Timetrap::Config)
|
|
10
|
+
@timetrap_config = timetrap_config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def email
|
|
14
|
+
config['email']
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def password
|
|
18
|
+
config['password']
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def subdomain
|
|
22
|
+
ensure_subdomain!
|
|
23
|
+
|
|
24
|
+
config['subdomain']
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def round_in_minutes
|
|
28
|
+
config['round_in_minutes'] || DEFAULT_ROUND_IN_MINUTES
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def alias_config(code)
|
|
32
|
+
if config = aliases[code]
|
|
33
|
+
config = config.split(' ')
|
|
34
|
+
|
|
35
|
+
{ project_id: config.first, task_id: config.last }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def aliases
|
|
40
|
+
ensure_aliases!
|
|
41
|
+
|
|
42
|
+
config['aliases']
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def config
|
|
46
|
+
ensure_config!
|
|
47
|
+
|
|
48
|
+
timetrap_config['harvest']
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ensure_config!
|
|
52
|
+
fail(MissingHarvestConfig, 'Missing harvest key in .timetrap.yml config file') if timetrap_config.nil? || timetrap_config['harvest'].nil?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ensure_aliases!
|
|
56
|
+
fail(MissingHarvestAliases, 'Missing aliases key in .timetrap.yml config file') if config['aliases'].nil?
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def ensure_subdomain!
|
|
60
|
+
fail(MissingHarvestSubdomain, 'Missing subdomain key in .timetrap.yml config file') if config['subdomain'].nil?
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class TimetrapHarvest::Formatter
|
|
2
|
+
HARVESTABLE_REGEX = /@(.*)/
|
|
3
|
+
|
|
4
|
+
attr_reader :entry, :config
|
|
5
|
+
|
|
6
|
+
def initialize(entry, config)
|
|
7
|
+
@entry = entry
|
|
8
|
+
@config = config
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def format
|
|
12
|
+
if alias_config
|
|
13
|
+
{ notes: entry[:note],
|
|
14
|
+
hours: hours_for_time(entry[:start], entry[:end]),
|
|
15
|
+
project_id: project_id.to_i,
|
|
16
|
+
task_id: task_id.to_i,
|
|
17
|
+
spent_at: entry[:start].strftime('%Y%m%d')
|
|
18
|
+
}
|
|
19
|
+
elsif code
|
|
20
|
+
{ error: 'Missing task alias config', note: entry[:note] }
|
|
21
|
+
else
|
|
22
|
+
{ error: 'No task alias provided', note: entry[:note] }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def project_id
|
|
27
|
+
alias_config[:project_id]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def task_id
|
|
31
|
+
alias_config[:task_id]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def alias_config
|
|
35
|
+
config.alias_config(code)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def round_in_minutes
|
|
39
|
+
config.round_in_minutes
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def code
|
|
43
|
+
if match = HARVESTABLE_REGEX.match(entry[:note])
|
|
44
|
+
code = match[1]
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def hours_for_time(start_time, end_time)
|
|
49
|
+
minutes = (end_time - start_time) / 60
|
|
50
|
+
rounded = round(minutes)
|
|
51
|
+
hours = (rounded / 60)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def round(value, nearest = round_in_minutes)
|
|
55
|
+
(value % nearest).zero? ? value : (value + nearest) - (value % nearest)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
class TimetrapHarvest::Harvester
|
|
2
|
+
attr_reader :results, :client
|
|
3
|
+
|
|
4
|
+
def initialize(results, client)
|
|
5
|
+
@results = results
|
|
6
|
+
@client = client
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def harvest
|
|
10
|
+
results.each do |result|
|
|
11
|
+
if result.key? :error
|
|
12
|
+
failed << result
|
|
13
|
+
else
|
|
14
|
+
client.post(result)
|
|
15
|
+
|
|
16
|
+
submitted << result
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
{ submitted: submitted, failed: failed }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def submitted
|
|
24
|
+
@submitted ||= []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def failed
|
|
28
|
+
@failed ||= []
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require 'net/http'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'uri'
|
|
4
|
+
|
|
5
|
+
class TimetrapHarvest::NetworkClient
|
|
6
|
+
attr_reader :email, :password, :subdomain
|
|
7
|
+
|
|
8
|
+
def initialize(config)
|
|
9
|
+
@email = config.email
|
|
10
|
+
@password = config.password
|
|
11
|
+
@subdomain = config.subdomain
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def post(payload)
|
|
15
|
+
req = Net::HTTP::Post.new(harvest_add_uri.request_uri)
|
|
16
|
+
req.basic_auth(email, password)
|
|
17
|
+
req.body = payload.to_json
|
|
18
|
+
req['Content-Type'] = 'application/json'
|
|
19
|
+
req['Accept'] = 'application/json'
|
|
20
|
+
|
|
21
|
+
res = Net::HTTP.start(harvest_add_uri.hostname, harvest_add_uri.port, use_ssl: true) do |http|
|
|
22
|
+
http.request(req)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def harvest_add_uri
|
|
27
|
+
URI("https://#{subdomain}.harvestapp.com/daily/add")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class TimetrapHarvest::Output
|
|
2
|
+
LINE_DIVIDER = '-' * 80
|
|
3
|
+
SUBMITTED_HEADER = "Submitted entries\n#{LINE_DIVIDER}"
|
|
4
|
+
FAILED_HEADER = "Failed entries\n#{LINE_DIVIDER}"
|
|
5
|
+
|
|
6
|
+
attr_reader :results
|
|
7
|
+
|
|
8
|
+
def initialize(results = {})
|
|
9
|
+
@results = results
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def generate
|
|
13
|
+
messages = [stats]
|
|
14
|
+
|
|
15
|
+
unless submitted.empty?
|
|
16
|
+
messages << SUBMITTED_HEADER
|
|
17
|
+
messages += submitted.map { |submitted| success_message(submitted[:notes]) }
|
|
18
|
+
messages << "\n"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
unless failed.empty?
|
|
22
|
+
messages << FAILED_HEADER
|
|
23
|
+
messages += failed.map { |failed| failed_message(failed[:note], failed[:error]) }
|
|
24
|
+
messages << "\n"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
messages.join("\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def stats
|
|
33
|
+
"Submitted: #{submitted.count}\nFailed: #{failed.count}\n"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def submitted
|
|
37
|
+
results.fetch(:submitted, [])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def failed
|
|
41
|
+
results.fetch(:failed, [])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def success_message(note)
|
|
45
|
+
"Submitted: #{note}"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def failed_message(note, error)
|
|
49
|
+
"Failed (#{error}): #{note}"
|
|
50
|
+
end
|
|
51
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: timetrap-harvest
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Devon Blandin
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2014-06-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: timetrap
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.7'
|
|
20
|
+
- - ">="
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: 1.7.0
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - "~>"
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.7'
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 1.7.0
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: rspec
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '3.0'
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: 3.0.0
|
|
43
|
+
type: :development
|
|
44
|
+
prerelease: false
|
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
46
|
+
requirements:
|
|
47
|
+
- - "~>"
|
|
48
|
+
- !ruby/object:Gem::Version
|
|
49
|
+
version: '3.0'
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: 3.0.0
|
|
53
|
+
- !ruby/object:Gem::Dependency
|
|
54
|
+
name: pry
|
|
55
|
+
requirement: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0.10'
|
|
60
|
+
- - ">="
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: 0.10.0
|
|
63
|
+
type: :development
|
|
64
|
+
prerelease: false
|
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
66
|
+
requirements:
|
|
67
|
+
- - "~>"
|
|
68
|
+
- !ruby/object:Gem::Version
|
|
69
|
+
version: '0.10'
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 0.10.0
|
|
73
|
+
description: |2
|
|
74
|
+
timetrap-harvest bridges the gap between your entries in Timetrap and your
|
|
75
|
+
project tasks in Harvest allowing for incredible easy timesheet
|
|
76
|
+
submissions.
|
|
77
|
+
email: dblandin@gmail.com
|
|
78
|
+
executables: []
|
|
79
|
+
extensions: []
|
|
80
|
+
extra_rdoc_files: []
|
|
81
|
+
files:
|
|
82
|
+
- lib/timetrap-harvest.rb
|
|
83
|
+
- lib/timetrap_harvest/config.rb
|
|
84
|
+
- lib/timetrap_harvest/formatter.rb
|
|
85
|
+
- lib/timetrap_harvest/harvester.rb
|
|
86
|
+
- lib/timetrap_harvest/network_client.rb
|
|
87
|
+
- lib/timetrap_harvest/output.rb
|
|
88
|
+
- lib/timetrap_harvest/version.rb
|
|
89
|
+
homepage: https://github.com/dblandin/timetrap-harvest
|
|
90
|
+
licenses:
|
|
91
|
+
- MIT
|
|
92
|
+
metadata: {}
|
|
93
|
+
post_install_message:
|
|
94
|
+
rdoc_options: []
|
|
95
|
+
require_paths:
|
|
96
|
+
- lib
|
|
97
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
98
|
+
requirements:
|
|
99
|
+
- - ">="
|
|
100
|
+
- !ruby/object:Gem::Version
|
|
101
|
+
version: '0'
|
|
102
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
|
+
requirements:
|
|
104
|
+
- - ">="
|
|
105
|
+
- !ruby/object:Gem::Version
|
|
106
|
+
version: '0'
|
|
107
|
+
requirements: []
|
|
108
|
+
rubyforge_project:
|
|
109
|
+
rubygems_version: 2.3.0
|
|
110
|
+
signing_key:
|
|
111
|
+
specification_version: 4
|
|
112
|
+
summary: A Harvest formatter for Timetrap
|
|
113
|
+
test_files: []
|