ruby_desk 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -1
- data/VERSION +1 -1
- data/lib/ruby_desk/connector.rb +61 -46
- data/lib/ruby_desk/provider.rb +10 -92
- data/lib/ruby_desk/snapshot.rb +35 -1
- data/lib/ruby_desk/team_room.rb +16 -9
- data/lib/ruby_desk/time_report.rb +119 -0
- data/lib/ruby_desk.rb +34 -28
- data/test/snapshot.json +2 -0
- data/test/teamrooms.json +2 -0
- data/test/test_ruby_desk.rb +105 -4
- data/test/timereport.json +1 -0
- data/test/workdiary.json +2 -0
- metadata +6 -2
- data/lib/ruby_desk/developer_skill.rb +0 -3
data/README.rdoc
CHANGED
@@ -6,7 +6,7 @@ A gem built by BadrIT (www.badrit.com) that works as an interface for oDesk APIs
|
|
6
6
|
Initialize with your api key
|
7
7
|
rd = RubyDesk::Connector.new(api_key, api_secret)
|
8
8
|
|
9
|
-
|
9
|
+
Get the URL that will ask the user to authenticate your application
|
10
10
|
rd.auth_url
|
11
11
|
|
12
12
|
You should then redirect the user to the returned URL to get the frob.
|
@@ -23,3 +23,4 @@ Now you are ready to use all the APIs you need
|
|
23
23
|
== Copyright
|
24
24
|
|
25
25
|
Copyright (c) 2010 Ahmed ElDawy. See LICENSE for details.
|
26
|
+
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.0
|
data/lib/ruby_desk/connector.rb
CHANGED
@@ -5,12 +5,15 @@ require 'net/https'
|
|
5
5
|
|
6
6
|
module RubyDesk
|
7
7
|
class Connector
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
ODESK_URL = "www.odesk.com/"
|
9
|
+
ODESK_API_URL = "#{ODESK_URL}api/"
|
10
|
+
ODESK_GDS_URL = "#{ODESK_URL}gds/"
|
11
|
+
ODESK_AUTH_URL = "#{ODESK_URL}services/api/auth/"
|
12
|
+
DEFAULT_OPTIONS = {:secure=>true, :sign=>true, :format=>'json',
|
13
|
+
:base_url=>ODESK_API_URL, :auth=>true, :params=>{} }
|
14
|
+
|
12
15
|
attr_writer :frob
|
13
|
-
|
16
|
+
|
14
17
|
def initialize(api_key=nil, api_secret=nil, frob=nil, api_token=nil)
|
15
18
|
@api_key = api_key
|
16
19
|
@api_secret = api_secret
|
@@ -19,46 +22,52 @@ module RubyDesk
|
|
19
22
|
@api_key = "991ac77f4c202873b0ab88f11762370c"
|
20
23
|
@api_secret = "c3382d5902e5a7b0"
|
21
24
|
end
|
22
|
-
|
23
|
-
# Sign the given parameters and returns the signature
|
25
|
+
|
26
|
+
# Sign the given parameters and returns the signature
|
24
27
|
def sign(params)
|
25
28
|
# sort parameters by its names (keys)
|
26
29
|
sorted_params = params.sort { |a, b| a.to_s <=> b.to_s}
|
27
|
-
|
30
|
+
|
28
31
|
# concatenate secret with names, values
|
29
32
|
concatenated = @api_secret + sorted_params.to_s
|
30
|
-
|
31
|
-
# Calculate md5 of concatenated string
|
32
|
-
|
33
|
-
|
34
|
-
# Return the calculated value as the signature
|
35
|
-
md5
|
33
|
+
|
34
|
+
# Calculate and return md5 of concatenated string
|
35
|
+
Digest::MD5.hexdigest(concatenated)
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
# Returns the correct URL to go to to invoke the given api
|
39
39
|
# path: the path of the API to call. e.g. 'auth'
|
40
|
-
# params: a hash of parameters that needs to be appended
|
41
40
|
# options:
|
42
41
|
# * :secure=>false: Whether a secure connection is required or not.
|
43
42
|
# * :sign=>true: Whether you need to sign the parameters or not
|
44
|
-
|
43
|
+
# * :params=>{}: a hash of parameters that needs to be appended
|
44
|
+
# * :auth=>true: when true indicates that this call need authentication.
|
45
|
+
# This forces adding :api_token, :api_key and :api_sig to parameters.
|
46
|
+
def prepare_api_call(path, options = {})
|
45
47
|
options = DEFAULT_OPTIONS.merge(options)
|
46
|
-
params
|
47
|
-
|
48
|
-
|
48
|
+
params = options[:params]
|
49
|
+
params[:api_sig] = sign(params) if options[:sign] || options[:auth]
|
50
|
+
if options[:auth]
|
51
|
+
params[:api_token] ||= @api_token
|
52
|
+
params[:api_key] ||= @api_key
|
53
|
+
end
|
54
|
+
url = (options[:secure] ? "https" : "http") + "://"
|
55
|
+
url << options[:base_url] << path
|
56
|
+
url << ".#{options[:format]}" if options[:format]
|
57
|
+
return {:url=>url, :params=> params, :method=>options[:method]}
|
49
58
|
end
|
50
|
-
|
59
|
+
|
51
60
|
# invokes the given API call and returns body of the response as text
|
52
61
|
def invoke_api_call(api_call)
|
53
62
|
url = URI.parse(api_call[:url])
|
54
63
|
http = Net::HTTP.new(url.host, url.port)
|
55
64
|
http.use_ssl = true
|
56
|
-
|
65
|
+
|
57
66
|
data = api_call[:params].to_a.map{|pair| pair.join '='}.join('&')
|
58
67
|
headers = {
|
59
68
|
'Content-Type' => 'application/x-www-form-urlencoded'
|
60
69
|
}
|
61
|
-
|
70
|
+
|
62
71
|
case api_call[:method]
|
63
72
|
when :get, 'get' then
|
64
73
|
resp, data = http.request(Net::HTTP::Get.new(url.path+"?"+data, headers))
|
@@ -67,28 +76,30 @@ module RubyDesk
|
|
67
76
|
when :delete, 'delete' then
|
68
77
|
resp, data = http.request(Net::HTTP::Delete.new(url.path, headers), data)
|
69
78
|
end
|
70
|
-
|
79
|
+
|
71
80
|
return data
|
72
81
|
end
|
73
|
-
|
82
|
+
|
74
83
|
# Prepares an API call with the given arguments then invokes it and returns its body
|
75
|
-
def prepare_and_invoke_api_call(path,
|
76
|
-
api_call = prepare_api_call(path,
|
84
|
+
def prepare_and_invoke_api_call(path, options = {})
|
85
|
+
api_call = prepare_api_call(path, options)
|
77
86
|
invoke_api_call(api_call)
|
78
87
|
end
|
79
|
-
|
88
|
+
|
80
89
|
# Returns the URL that authenticates the application for the current user
|
81
90
|
def auth_url
|
82
|
-
|
83
|
-
|
84
|
-
|
91
|
+
auth_call = prepare_api_call("", :params=>{:api_key=>@api_key},
|
92
|
+
:base_url=>ODESK_AUTH_URL)
|
93
|
+
return auth_call[:url]
|
85
94
|
end
|
86
|
-
|
95
|
+
|
87
96
|
# return the URL that logs user out of odesk applications
|
88
97
|
def logout_url
|
89
|
-
"
|
98
|
+
logout_call = prepare_api_call("", :base_url=>ODESK_AUTH_URL,
|
99
|
+
:secure=>false)
|
100
|
+
return logout_call[:url]
|
90
101
|
end
|
91
|
-
|
102
|
+
|
92
103
|
# Returns an authentication frob.
|
93
104
|
# Parameters
|
94
105
|
# * frob
|
@@ -98,11 +109,13 @@ module RubyDesk
|
|
98
109
|
# Return Data
|
99
110
|
# * token
|
100
111
|
def get_token
|
101
|
-
response = prepare_and_invoke_api_call 'auth/v1/keys/tokens',
|
112
|
+
response = prepare_and_invoke_api_call 'auth/v1/keys/tokens',
|
113
|
+
:params=>{:frob=>@frob, :api_key=>@api_key}, :method=>:post,
|
114
|
+
:auth=>false
|
102
115
|
json = JSON.parse(response)
|
103
116
|
@api_token = json['token']
|
104
117
|
end
|
105
|
-
|
118
|
+
|
106
119
|
# Returns an authentication frob.
|
107
120
|
#Parameters
|
108
121
|
# * api_key
|
@@ -110,25 +123,26 @@ module RubyDesk
|
|
110
123
|
#
|
111
124
|
#Return Data
|
112
125
|
# * frob
|
113
|
-
|
126
|
+
|
114
127
|
def get_frob
|
115
|
-
response = prepare_and_invoke_api_call 'auth/v1/keys/frobs',
|
128
|
+
response = prepare_and_invoke_api_call 'auth/v1/keys/frobs',
|
129
|
+
:params=>{:api_key=>@api_key}, :method=>:post, :auth=>false
|
116
130
|
json = JSON.parse(response)
|
117
131
|
@frob = json['frob']
|
118
132
|
end
|
119
|
-
|
133
|
+
|
120
134
|
# Returns the authenticated user associated with the given authorization token.
|
121
135
|
# Parameters
|
122
|
-
#
|
136
|
+
#
|
123
137
|
# * api_key
|
124
138
|
# * api_sig
|
125
139
|
# * api_token
|
126
|
-
#
|
140
|
+
#
|
127
141
|
# Return Data
|
128
|
-
#
|
129
|
-
# * token
|
142
|
+
#
|
143
|
+
# * token
|
130
144
|
def check_token
|
131
|
-
prepare_and_invoke_api_call 'auth/v1/keys/token',
|
145
|
+
prepare_and_invoke_api_call 'auth/v1/keys/token', :method=>:get
|
132
146
|
json = JSON.parse(response)
|
133
147
|
# TODO what to make with results?
|
134
148
|
return json
|
@@ -141,9 +155,10 @@ module RubyDesk
|
|
141
155
|
# * api_token
|
142
156
|
|
143
157
|
def revoke_token
|
144
|
-
prepare_and_invoke_api_call 'auth/v1/keys/token',
|
158
|
+
prepare_and_invoke_api_call 'auth/v1/keys/token', :method=>:delete
|
145
159
|
@api_token = nil
|
146
160
|
end
|
147
|
-
|
161
|
+
|
148
162
|
end
|
149
163
|
end
|
164
|
+
|
data/lib/ruby_desk/provider.rb
CHANGED
@@ -1,91 +1,7 @@
|
|
1
1
|
class RubyDesk::Provider < RubyDesk::OdeskEntity
|
2
2
|
class << self
|
3
3
|
def all_categories
|
4
|
-
text =
|
5
|
-
Web Development
|
6
|
-
* Web Design
|
7
|
-
* Web Programming
|
8
|
-
* Ecommerce
|
9
|
-
* UI Design
|
10
|
-
* Website QA
|
11
|
-
* Website Project Management
|
12
|
-
* Other - Web Development
|
13
|
-
Software Development
|
14
|
-
* Desktop Applications
|
15
|
-
* Game Development
|
16
|
-
* Scripts & Utilities
|
17
|
-
* Software Plug-ins
|
18
|
-
* Mobile Apps
|
19
|
-
* Application Interface Design
|
20
|
-
* Software Project Management
|
21
|
-
* Software QA
|
22
|
-
* VOIP
|
23
|
-
* Other - Software Development
|
24
|
-
Networking & Information Systems
|
25
|
-
* Network Administration
|
26
|
-
* DBA - Database Administration
|
27
|
-
* Server Administration
|
28
|
-
* ERP / CRM Implementation
|
29
|
-
* Other - Networking & Information Systems
|
30
|
-
Writing & Translation
|
31
|
-
* Technical Writing
|
32
|
-
* Website Content
|
33
|
-
* Blog & Article Writing
|
34
|
-
* Copywriting
|
35
|
-
* Translation
|
36
|
-
* Creative Writing
|
37
|
-
* Other - Writing & Translation
|
38
|
-
Administrative Support
|
39
|
-
* Data Entry
|
40
|
-
* Personal Assistant
|
41
|
-
* Web Research
|
42
|
-
* Email Response Handling
|
43
|
-
* Transcription
|
44
|
-
* Other - Administrative Support
|
45
|
-
Design & Multimedia
|
46
|
-
* Graphic Design
|
47
|
-
* Logo Design
|
48
|
-
* Illustration
|
49
|
-
* Print Design
|
50
|
-
* 3D Modeling & CAD
|
51
|
-
* Audio Production
|
52
|
-
* Video Production
|
53
|
-
* Voice Talent
|
54
|
-
* Animation
|
55
|
-
* Presentations
|
56
|
-
* Engineering & Technical Design
|
57
|
-
* Other - Design & Multimedia
|
58
|
-
Customer Service
|
59
|
-
* Customer Service & Support
|
60
|
-
* Technical support
|
61
|
-
* Phone Support
|
62
|
-
* Order Processing
|
63
|
-
* Other - Customer Service
|
64
|
-
Sales & Marketing
|
65
|
-
* Advertising
|
66
|
-
* Email Marketing
|
67
|
-
* SEO - Search Engine Optimization
|
68
|
-
* SEM - Search Engine Marketing
|
69
|
-
* SMM - Social Media Marketing
|
70
|
-
* PR - Public Relations
|
71
|
-
* Telemarketing & Telesales
|
72
|
-
* Business Plans & Marketing Strategy
|
73
|
-
* Market Research & Surveys
|
74
|
-
* Sales & Lead Generation
|
75
|
-
* Other - Sales & Marketing
|
76
|
-
Business Services
|
77
|
-
* Accounting
|
78
|
-
* Bookkeeping
|
79
|
-
* HR / Payroll
|
80
|
-
* Financial Services & Planning
|
81
|
-
* Payment Processing
|
82
|
-
* Legal
|
83
|
-
* Project Management
|
84
|
-
* Business Consulting
|
85
|
-
* Recruiting
|
86
|
-
* Statistical Analysis
|
87
|
-
* Other - Business Services
|
88
|
-
CATEGORIES
|
4
|
+
text = File.read(File.join(File.dirname(__FILE__), "..", "categories"))
|
89
5
|
lines = text.split(/[\r\n]+/).map{|line| line.strip}
|
90
6
|
categories = {}
|
91
7
|
subcategories = nil
|
@@ -98,7 +14,7 @@ class RubyDesk::Provider < RubyDesk::OdeskEntity
|
|
98
14
|
end
|
99
15
|
return categories
|
100
16
|
end
|
101
|
-
|
17
|
+
|
102
18
|
# Implements search with the given criteria
|
103
19
|
#* q - ProfileData (ProfileData)
|
104
20
|
# * Profile data is any text that appears in a provider's profile. For example if q = 'odesk' the search would return any user's with the word odesk in their profile.
|
@@ -158,7 +74,8 @@ class RubyDesk::Provider < RubyDesk::OdeskEntity
|
|
158
74
|
if options.respond_to? :to_str
|
159
75
|
return search(connector, :q=>options.to_str)
|
160
76
|
end
|
161
|
-
response = connector.prepare_and_invoke_api_call
|
77
|
+
response = connector.prepare_and_invoke_api_call(
|
78
|
+
'profiles/v1/search/providers', :method=>:get)
|
162
79
|
# parses a JSON result returned from oDesk and extracts an array of Providers out of it
|
163
80
|
json = JSON.parse(response)
|
164
81
|
providers = []
|
@@ -167,11 +84,11 @@ class RubyDesk::Provider < RubyDesk::OdeskEntity
|
|
167
84
|
end
|
168
85
|
return providers
|
169
86
|
end
|
170
|
-
|
87
|
+
|
171
88
|
def get_profile(connector, id, options={})
|
172
89
|
brief = options.delete :brief || false
|
173
|
-
response = connector.prepare_and_invoke_api_call(
|
174
|
-
|
90
|
+
response = connector.prepare_and_invoke_api_call(
|
91
|
+
"profiles/v1/providers/#{id}" + (brief ? "/brief" : ""), :method=>:get)
|
175
92
|
json = JSON.parse(response)
|
176
93
|
return self.new(json['profile'])
|
177
94
|
end
|
@@ -203,7 +120,7 @@ class RubyDesk::Provider < RubyDesk::OdeskEntity
|
|
203
120
|
:ag_adj_score, :dev_recent_hours, :dev_timezone, :ag_country_tz, :ag_city,
|
204
121
|
:dev_test_passed_count, :dev_tot_feedback, :ag_summary, :ag_manager_name,
|
205
122
|
:ag_active_assignments, :portfolio_items
|
206
|
-
|
123
|
+
|
207
124
|
attribute :skills, :class=>RubyDesk::Skill, :sub_element=>'skill'
|
208
125
|
attribute :dev_scores, :class=>RubyDesk::DeveloperSkill, :sub_element=>'dev_score'
|
209
126
|
attribute :dev_ac_agencies, :class=>RubyDesk::Agency, :sub_element=>'dev_ac_agency'
|
@@ -228,4 +145,5 @@ class RubyDesk::Provider < RubyDesk::OdeskEntity
|
|
228
145
|
end
|
229
146
|
end
|
230
147
|
end
|
231
|
-
end
|
148
|
+
end
|
149
|
+
|
data/lib/ruby_desk/snapshot.rb
CHANGED
@@ -8,4 +8,38 @@ class RubyDesk::Snapshot < RubyDesk::OdeskEntity
|
|
8
8
|
|
9
9
|
attribute :user, :class=>RubyDesk::User
|
10
10
|
|
11
|
-
|
11
|
+
def self.work_diary(connector, company_id, user_id, date = nil, timezone = "mine")
|
12
|
+
response = connector.prepare_and_invoke_api_call(
|
13
|
+
"team/v1/workdiaries/#{company_id}/#{user_id}" + (date ? "/"+date : ""),
|
14
|
+
:params=>{:timezone=>timezone}, :method=>:get)
|
15
|
+
json = JSON.parse(response)
|
16
|
+
|
17
|
+
return nil unless json['snapshots']['snapshot']
|
18
|
+
[json['snapshots']['snapshot']].flatten.map do |snapshot|
|
19
|
+
self.new(snapshot)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.snapshot_details(connector, company_id, user_id, timestamp = nil)
|
24
|
+
timestamp_param = case timestamp
|
25
|
+
when String then timestamp
|
26
|
+
when Date, Time then timestamp.strftime("%Y%m%d")
|
27
|
+
when Range then [timestamp.first, timestamp.last].map{|t|t.strftime("%Y%m%d")}.join(",")
|
28
|
+
when Array then timestamp.map{|t| t.strftime("%Y%m%d")}.join(";")
|
29
|
+
end
|
30
|
+
# Invoke API call
|
31
|
+
response = connector.prepare_and_invoke_api_call(
|
32
|
+
"team/v1/workdiaries/#{company_id}/#{user_id}" +
|
33
|
+
(timestamp_param ? "/#{timestamp_param}" : ""), :method=>:get)
|
34
|
+
|
35
|
+
# Parse result in json format
|
36
|
+
json = JSON.parse(response)
|
37
|
+
|
38
|
+
# Generate ruby objects for each snapshot
|
39
|
+
[json['snapshot']].flatten.map do |snapshot|
|
40
|
+
self.new(snapshot)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
data/lib/ruby_desk/team_room.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
class RubyDesk::TeamRoom
|
2
|
+
# Attribute readers for all attributes
|
3
|
+
attr_reader :company_recno, :company_name, :name, :id, :recno, :teamroom_api
|
4
|
+
|
2
5
|
class << self
|
3
6
|
def get_teamrooms(connector)
|
4
|
-
response = connector.prepare_and_invoke_api_call 'team/v1/teamrooms',
|
7
|
+
response = connector.prepare_and_invoke_api_call 'team/v1/teamrooms',
|
8
|
+
:method=>:get
|
5
9
|
# parses a JSON result returned from oDesk and extracts an array of TeamRooms out of it
|
6
10
|
json = JSON.parse(response)
|
7
11
|
team_rooms = []
|
@@ -12,12 +16,9 @@ class RubyDesk::TeamRoom
|
|
12
16
|
# return the resulting array
|
13
17
|
team_rooms
|
14
18
|
end
|
15
|
-
|
19
|
+
|
16
20
|
end
|
17
|
-
|
18
|
-
# Attribute readers for all attributes
|
19
|
-
attr_reader :company_recno, :company_name, :name, :id, :recno, :teamroom_api
|
20
|
-
|
21
|
+
|
21
22
|
# Create a new TeamRoom from a hash similar to the one in ActiveRecord::Base.
|
22
23
|
# The given hash maps each attribute name to its value
|
23
24
|
def initialize(params={})
|
@@ -25,10 +26,16 @@ class RubyDesk::TeamRoom
|
|
25
26
|
self.instance_variable_set("@#{k}", v)
|
26
27
|
end
|
27
28
|
end
|
28
|
-
|
29
|
+
|
29
30
|
def snapshot(connector, online='now')
|
30
|
-
response = connector.prepare_and_invoke_api_call "team/v1/teamrooms/#{self.id}",
|
31
|
+
response = connector.prepare_and_invoke_api_call "team/v1/teamrooms/#{self.id}",
|
32
|
+
:params=>{:online=>online}, :method=>:get
|
31
33
|
json = JSON.parse(response)
|
32
34
|
RubyDesk::Snapshot.new(json['teamroom']['snapshot'])
|
33
35
|
end
|
34
|
-
|
36
|
+
|
37
|
+
def work_diary(connector, user_id, date = nil, timezone = "mine")
|
38
|
+
RubyDesk::Snapshot.workdiary(connector, self.id, user_id, date, timezone)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
class RubyDesk::TimeReport
|
5
|
+
DEFAULT_OPTIONS = {:select => "worked_on, provider_id, sum(hours)",
|
6
|
+
:conditions=>{} }
|
7
|
+
|
8
|
+
# The time reports API uses the Google Visualization API's query language to
|
9
|
+
# run detailed reports on a user's team, company, as well as provider
|
10
|
+
# specific detail.
|
11
|
+
# In order to use the reports API you need to understand the data model
|
12
|
+
# accessible as well as the supported Google query language elements.
|
13
|
+
# Please see the
|
14
|
+
# <a href="http://developers.odesk.com/oDesk-Google-Data-Source-Language">
|
15
|
+
# oDesk Google Data Source Language</a> page for a detailed description of
|
16
|
+
# what's available.
|
17
|
+
# For more details around how to construct data parameters see the
|
18
|
+
# <a href="http://code.google.com/apis/visualization/documentation/querylanguage.html">
|
19
|
+
# google documentation</a>.
|
20
|
+
#
|
21
|
+
# Allowed values for options are
|
22
|
+
# :select
|
23
|
+
# :condition
|
24
|
+
# Here is the limited support for the where clause.
|
25
|
+
# Fields
|
26
|
+
# * Name
|
27
|
+
# * company_id
|
28
|
+
# * agency_id
|
29
|
+
# * provider_id
|
30
|
+
# * worked_on
|
31
|
+
# * assignment_team_id
|
32
|
+
# * provider_id
|
33
|
+
# * task
|
34
|
+
# :order
|
35
|
+
def self.find(connector, options={})
|
36
|
+
options = DEFAULT_OPTIONS.merge(options)
|
37
|
+
call_url = "timereports/v1"
|
38
|
+
# Adjust a URL that has as much information as we can
|
39
|
+
if String === options[:conditions][:company_id]
|
40
|
+
# Limit to one company in url (better looking)
|
41
|
+
call_url << "/companies/#{options[:conditions].delete :company_id}"
|
42
|
+
|
43
|
+
if String === options[:conditions][:agency_id]
|
44
|
+
# Limit to one agency in url (better looking)
|
45
|
+
call_url << "/agencies/#{options[:conditions].delete :agency_id}"
|
46
|
+
elsif String === options[:conditions][:team_id]
|
47
|
+
# Limit to one team in url (better looking)
|
48
|
+
call_url << "/teams/#{options[:conditions].delete :team_id}"
|
49
|
+
end
|
50
|
+
elsif String === options[:conditions][:provider_id]
|
51
|
+
call_url << "/providers/#{options[:conditions].delete :provider_id}"
|
52
|
+
end
|
53
|
+
|
54
|
+
call_url << "/hours" if options[:hours]
|
55
|
+
|
56
|
+
gds_query = build_query(options)
|
57
|
+
response = connector.prepare_and_invoke_api_call(call_url, :method=>:get,
|
58
|
+
:base_url=>RubyDesk::Connector::ODESK_GDS_URL, :format=>nil,
|
59
|
+
:params=>{:tq=>URI.escape(gds_query," ,%&=./"), :tqx=>"out:json"})
|
60
|
+
|
61
|
+
json = JSON.parse(response)
|
62
|
+
columns = [json['table']['cols']].flatten
|
63
|
+
rows = [json['table']['rows']].flatten.map do |row_data|
|
64
|
+
row = {}
|
65
|
+
[row_data['c']].flatten.each_with_index do |row_element, element_index|
|
66
|
+
column = columns[element_index]
|
67
|
+
row[column['label']] = str_to_value(row_element['v'], column['type'])
|
68
|
+
end
|
69
|
+
row
|
70
|
+
end
|
71
|
+
return rows
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.build_query(options)
|
75
|
+
gds_query = ""
|
76
|
+
gds_query << "SELECT " << options[:select]
|
77
|
+
if options[:conditions]
|
78
|
+
gds_query << " WHERE "
|
79
|
+
combined = options[:conditions].map do |field, condition|
|
80
|
+
"(" +
|
81
|
+
case condition
|
82
|
+
when String, Numeric, Date, Time then
|
83
|
+
"#{field}=#{value_to_str(condition)}"
|
84
|
+
when Array then
|
85
|
+
condition.map{|v| "#{field}=#{value_to_str(v)}"}.join(" OR ")
|
86
|
+
when Range
|
87
|
+
"#{field}>=#{value_to_str(condition.first)} AND #{field}" +
|
88
|
+
(condition.exclude_end? ? "<" : "<=") +
|
89
|
+
value_to_str(condition.last)
|
90
|
+
end +
|
91
|
+
")"
|
92
|
+
end
|
93
|
+
gds_query << combined.join(" AND ")
|
94
|
+
end
|
95
|
+
if options[:order]
|
96
|
+
gds_query << " ORDER BY " << options[:order]
|
97
|
+
end
|
98
|
+
gds_query
|
99
|
+
end
|
100
|
+
|
101
|
+
def self.value_to_str(value)
|
102
|
+
case value
|
103
|
+
when String then value
|
104
|
+
when Date, Time then "'"+value.strftime("%Y-%m-%d")+"'"
|
105
|
+
when Numeric then value.to_s
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.str_to_value(string, type)
|
110
|
+
case type
|
111
|
+
when 'number' then string.to_f
|
112
|
+
when 'date' then Date.strptime(string, "%Y%m%d")
|
113
|
+
when 'string' then string
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
end
|
119
|
+
|
data/lib/ruby_desk.rb
CHANGED
@@ -14,43 +14,49 @@ module RubyDesk
|
|
14
14
|
attributes *attribute_names
|
15
15
|
end
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
Competency = define_simple_class(:cmp_url, :cmp_when, :cmp_provider, :cmp_ref,
|
19
|
-
|
20
|
-
Skill = define_simple_class(:skl_level_num, :skl_ref, :skl_name,
|
21
|
-
|
22
|
-
Agency = define_simple_class(:ag_adj_score, :ag_logo, :ag_name,
|
23
|
-
|
19
|
+
:cmp_percentile, :cmp_name, :cmp_recno, :cmp_score, :cmp_id)
|
20
|
+
Skill = define_simple_class(:skl_level_num, :skl_ref, :skl_name,
|
21
|
+
:skl_last_used, :skl_recno, :skl_level, :skl_comment, :skl_year_exp)
|
22
|
+
Agency = define_simple_class(:ag_adj_score, :ag_logo, :ag_name,
|
23
|
+
:ag_recent_hours, :ag_tot_feedback, :ag_recno, :ciphertext,
|
24
|
+
:ag_adj_score_recent, :ag_total_hours)
|
24
25
|
Exam = define_simple_class(:ts_name, :ts_rank, :ts_duration, :ts_when,
|
25
|
-
|
26
|
-
|
26
|
+
:ts_provider, :ts_score, :ts_percentile, :ts_curl_name, :ts_ref,
|
27
|
+
:ts_is_certification, :ts_id, :ts_pass, :ts_seo_link)
|
27
28
|
JobCategory = define_simple_class(:seo_link, :second_level, :first_level)
|
28
29
|
Experience = define_simple_class(:exp_comment, :exp_from, :exp_recno,
|
29
|
-
|
30
|
+
:exp_title, :exp_company, :exp_to)
|
30
31
|
Candidacy = define_simple_class(:permalink, :dev_recno, :opening_ciphertext,
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Assignment = define_simple_class(:as_type, :as_blended_cost_rate,
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
Certificate = define_simple_class(:cmp_url, :cmp_when, :cmp_provider,
|
40
|
-
|
32
|
+
:rel_requested_assignment, :created_type, :developer_ciphertext,
|
33
|
+
:ciphertext, :create_date, :profile_seo_link, :Agencyref,
|
34
|
+
:agency_ciphertext, :op_title, :record_id, :job_profile_access)
|
35
|
+
Assignment = define_simple_class(:as_type, :as_blended_cost_rate,
|
36
|
+
:as_total_hours, :as_to, :as_from, :as_client, :as_opening_access,
|
37
|
+
:as_agency_name, :as_rate, :as_blended_charge_rate, :as_amount,
|
38
|
+
:as_total_charge, :as_opening_recno, :as_description, :as_opening_title,
|
39
|
+
:as_status, :as_job_type, :as_ciphertext_opening_recno, :as_total_cost)
|
40
|
+
Certificate = define_simple_class(:cmp_url, :cmp_when, :cmp_provider,
|
41
|
+
:cmp_ref, :cmp_percentile, :cmp_name, :cmp_recno, :cmp_score, :cmp_id)
|
41
42
|
Institution = define_simple_class(:ed_area, :ed_school, :ed_degree, :ed_to,
|
42
|
-
|
43
|
-
OtherExperience = define_simple_class(:exp_recno, :exp_description,
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
43
|
+
:ed_recno, :ed_comment, :ed_from)
|
44
|
+
OtherExperience = define_simple_class(:exp_recno, :exp_description,
|
45
|
+
:exp_subject)
|
46
|
+
Trend = define_simple_class(:trend_word, :trend_url, :trend_recno,
|
47
|
+
:trend_skill)
|
48
|
+
PortfolioItem = define_simple_class(:pi_recno, :pi_description,
|
49
|
+
:pi_attachment, :pi_completed, :pi_thumbnail, :pi_title, :pi_category,
|
50
|
+
:pi_url, :pi_image)
|
51
|
+
User = define_simple_class(:messenger_id, :timezone, :uid, :timezone_offset,
|
52
|
+
:last_name, :mail, :creation_time, :first_name)
|
53
|
+
DeveloperSkill = define_simple_class(:order, :description,
|
54
|
+
:avg_category_score_recent, :avg_category_score, :label)
|
50
55
|
# = define_simple_class()
|
51
56
|
# = define_simple_class()
|
52
57
|
end
|
53
58
|
|
54
59
|
Dir.glob(File.join(File.dirname(__FILE__), 'ruby_desk', '*.rb')).each do |file|
|
55
60
|
require file
|
56
|
-
end
|
61
|
+
end
|
62
|
+
|
data/test/snapshot.json
ADDED
@@ -0,0 +1,2 @@
|
|
1
|
+
{"server_time":1263130514,"auth_user":{"first_name":"Ahmed El-Dawy","last_name":".","uid":"aseldawy","mail":"aseldawy@odesk.com","messenger_id":"aseldawy","messenger_type":"yahoo","timezone":"Africa\/Cairo","timezone_offset":"7200"},"snapshot":{"status":"PRIVATE","time":"1249344000","billing_status":"non-billed.disconnected","activity":"0","online_presence":"0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0","user":{"messenger_id":"aseldawy","timezone":"Africa\/Cairo","uid":"aseldawy","timezone_offset":"7200","last_name":".","messenger_type":"yahoo","mail":"aseldawy@odesk.com","creation_time":"1207503924","archiving_time":"1262563200","first_name":"Ahmed El-Dawy"},"mouse_events_count":"","company_id":"techunlimited","timezone":"Africa\/Cairo","uid":"","keyboard_events_count":"","last_worked":"1258037998","memo":"","active_window_title":"","report24_img":"http:\/\/chart.apis.google.com\/chart?chs=96x17&cht=ls&chco=cccccc&chf=bg,s,f8f8f8&chm=B,cccccc,0,0,0&chd=t:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0","computer_name":"","online_presence_img":"http:\/\/chart.apis.google.com\/chart?chs=96x17&cht=ls&chco=cccccc&chf=bg,s,f8f8f8&chm=B,cccccc,0,0,0&chd=t:0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0","user_id":"aseldawy","client_version":"","teamroom_api":"\/api\/team\/v1\/teamrooms\/techunlimited.json","workdiary_api":"\/api\/team\/v1\/workdiaries\/techunlimited\/aseldawy\/20090804.json"}}
|
2
|
+
|
data/test/teamrooms.json
ADDED
@@ -0,0 +1,2 @@
|
|
1
|
+
{"server_time":"1263125223","auth_user":{"first_name":"Ahmed El-Dawy","last_name":".","uid":"aseldawy","mail":"aseldawy@odesk.com","messenger_id":"aseldawy","messenger_type":"yahoo","timezone":"Africa\/Cairo","timezone_offset":"7200"},"teamrooms":{"teamroom":[{"company_recno":"28705","company_name":"Tech Unlimited, Inc.","name":"Tech Unlimited, Inc.","id":"techunlimited","recno":"28705","teamroom_api":"\/api\/team\/v1\/teamrooms\/techunlimited.json"},{"company_recno":"81112","company_name":"Arabic Development Int.","name":"Phase One","id":"tejahcom","recno":"81112","teamroom_api":"\/api\/team\/v1\/teamrooms\/tejahcom.json"},{"company_recno":"16273","company_name":"Techniprise Inc.","name":"Techniprise Inc.","id":"techniprise","recno":"16273","teamroom_api":"\/api\/team\/v1\/teamrooms\/techniprise.json"},{"company_recno":"33618","company_name":"BadrIT","name":"BadrIT","id":"badrit","recno":"33618","teamroom_api":"\/api\/team\/v1\/teamrooms\/badrit.json"},{"company_recno":"51737","company_name":"Lian AlKhaleej","name":"Lian AlKhaleej","id":"lianalkhaleej:lianalkhaleej","recno":"51737","teamroom_api":"\/api\/team\/v1\/teamrooms\/lianalkhaleej:lianalkhaleej.json"}]},"teamroom":""}
|
2
|
+
|
data/test/test_ruby_desk.rb
CHANGED
@@ -1,14 +1,44 @@
|
|
1
1
|
require 'helper'
|
2
|
+
require 'date'
|
2
3
|
|
3
4
|
class TestRubyDesk < Test::Unit::TestCase
|
5
|
+
def test_sign
|
6
|
+
connector = RubyDesk::Connector.new('824d225a889aca186c55ac49a6b23957',
|
7
|
+
'984aa36db13fff5c')
|
8
|
+
assert_equal 'dc30357f7c6b62aadf99e5313ac93365',
|
9
|
+
connector.sign(:online=>'all', :tz=>'mine',
|
10
|
+
:api_key=>'824d225a889aca186c55ac49a6b23957',
|
11
|
+
:api_token=>'ffffffffffffffffffffffffffffffff')
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_api_call
|
15
|
+
connector = RubyDesk::Connector.new('824d225a889aca186c55ac49a6b23957',
|
16
|
+
'984aa36db13fff5c')
|
17
|
+
api_call = connector.prepare_api_call('auth/v1/keys/tokens',
|
18
|
+
:params=>{:frob=>@frob, :api_key=>@api_key},
|
19
|
+
:method=>:post, :format=>'json')
|
20
|
+
assert api_call[:url].index('.json')
|
21
|
+
assert api_call[:url].index('/api/auth/v1/keys/tokens')
|
22
|
+
assert api_call[:params][:api_sig]
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_gds_api_call
|
26
|
+
connector = RubyDesk::Connector.new('824d225a889aca186c55ac49a6b23957',
|
27
|
+
'984aa36db13fff5c')
|
28
|
+
api_call = connector.prepare_api_call('timereports/v1/companies/1',
|
29
|
+
:method=>:post, :base_url=>RubyDesk::Connector::ODESK_GDS_URL)
|
30
|
+
assert api_call[:url].index('/gds/timereports/v1/companies/1')
|
31
|
+
assert api_call[:params][:api_sig]
|
32
|
+
end
|
33
|
+
|
4
34
|
def test_provider_search
|
5
35
|
connector = Object.new
|
6
36
|
def connector.prepare_and_invoke_api_call(*args)
|
7
37
|
File.read(File.join(File.dirname(__FILE__), 'providers.json'))
|
8
38
|
end
|
9
|
-
|
39
|
+
|
10
40
|
providers = RubyDesk::Provider.search(connector, 'rails')
|
11
|
-
|
41
|
+
|
12
42
|
assert providers.is_a?(Array)
|
13
43
|
assert_equal 20, providers.size
|
14
44
|
assert_equal RubyDesk::Provider, providers.first.class
|
@@ -20,11 +50,82 @@ class TestRubyDesk < Test::Unit::TestCase
|
|
20
50
|
def connector.prepare_and_invoke_api_call(*args)
|
21
51
|
File.read(File.join(File.dirname(__FILE__), 'profile.json'))
|
22
52
|
end
|
23
|
-
|
53
|
+
|
24
54
|
profile = RubyDesk::Provider.get_profile(connector, 'aseldawy')
|
25
|
-
|
55
|
+
|
26
56
|
assert_equal RubyDesk::Provider, profile.class
|
27
57
|
assert_equal RubyDesk::Skill, profile.skills.first.class
|
28
58
|
assert_equal RubyDesk::Competency, profile.competencies.first.class
|
29
59
|
end
|
60
|
+
|
61
|
+
def test_team_rooms
|
62
|
+
connector = Object.new
|
63
|
+
def connector.prepare_and_invoke_api_call(*args)
|
64
|
+
File.read(File.join(File.dirname(__FILE__), 'teamrooms.json'))
|
65
|
+
end
|
66
|
+
|
67
|
+
teamrooms = RubyDesk::TeamRoom.get_teamrooms(connector)
|
68
|
+
|
69
|
+
assert_equal 5, teamrooms.size
|
70
|
+
assert_equal RubyDesk::TeamRoom, teamrooms.first.class
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_workdiary
|
74
|
+
connector = Object.new
|
75
|
+
def connector.prepare_and_invoke_api_call(*args)
|
76
|
+
File.read(File.join(File.dirname(__FILE__), 'workdiary.json'))
|
77
|
+
end
|
78
|
+
|
79
|
+
snapshots = RubyDesk::Snapshot.work_diary(connector, 'techunlimited',
|
80
|
+
'aseldawy')
|
81
|
+
assert_equal 14, snapshots.size
|
82
|
+
assert_equal RubyDesk::Snapshot, snapshots.first.class
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_snapshot
|
86
|
+
connector = Object.new
|
87
|
+
def connector.prepare_and_invoke_api_call(*args)
|
88
|
+
File.read(File.join(File.dirname(__FILE__), 'snapshot.json'))
|
89
|
+
end
|
90
|
+
|
91
|
+
snapshots = RubyDesk::Snapshot.snapshot_details(connector, 'techunlimited',
|
92
|
+
'aseldawy', Time.now)
|
93
|
+
assert_equal 1, snapshots.size
|
94
|
+
assert_equal RubyDesk::Snapshot, snapshots.first.class
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_query_builder
|
98
|
+
test_data = [
|
99
|
+
[{:select=>"worked_on", :conditions=>{:worked_on=>Time.mktime(2009, 10, 10)}},
|
100
|
+
"SELECT worked_on WHERE (worked_on='2009-10-10')"],
|
101
|
+
[{:select=>"worked_on, provider_id", :conditions=>{:provider_id=>[1,2,3]}},
|
102
|
+
"SELECT worked_on, provider_id WHERE (provider_id=1 OR provider_id=2 OR provider_id=3)"],
|
103
|
+
[{:select=>"worked_on", :conditions=>{:provider_id=>1, :agency_id=>3}},
|
104
|
+
["SELECT worked_on WHERE (agency_id=3) AND (provider_id=1)",
|
105
|
+
"SELECT worked_on WHERE (provider_id=1) AND (agency_id=3)"]],
|
106
|
+
[{:select=>"worked_on", :conditions=>{:provider_id=>1..3}},
|
107
|
+
"SELECT worked_on WHERE (provider_id>=1 AND provider_id<=3)"],
|
108
|
+
[{:select=>"worked_on", :conditions=>{:provider_id=>1...3}},
|
109
|
+
"SELECT worked_on WHERE (provider_id>=1 AND provider_id<3)"],
|
110
|
+
]
|
111
|
+
test_data.each do |options, query|
|
112
|
+
assert query.include?(RubyDesk::TimeReport.build_query(options))
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_timreport
|
117
|
+
connector = Object.new
|
118
|
+
def connector.prepare_and_invoke_api_call(*args)
|
119
|
+
File.read(File.join(File.dirname(__FILE__), 'timereport.json'))
|
120
|
+
end
|
121
|
+
|
122
|
+
timereport = RubyDesk::TimeReport.find(connector,
|
123
|
+
:select=>"worked_on, sum(hours), provider_id")
|
124
|
+
assert_equal 15, timereport.size
|
125
|
+
assert_equal Hash, timereport.first.class
|
126
|
+
assert_equal Date, timereport.first['worked_on'].class
|
127
|
+
assert timereport.first['hours'].is_a?(Numeric)
|
128
|
+
end
|
129
|
+
|
30
130
|
end
|
131
|
+
|
@@ -0,0 +1 @@
|
|
1
|
+
{"server_time":"1263311702","auth_user":{"first_name":"Ahmed El-Dawy","last_name":".","uid":"aseldawy","mail":"aseldawy@odesk.com","messenger_id":"aseldawy","messenger_type":"yahoo","timezone":"Africa\/Cairo","timezone_offset":"7200"},"table":{"cols":[{"type":"date","label":"worked_on"},{"type":"number","label":"hours"},{"type":"string","label":"provider_id"}],"rows":[{"c":[{"v":"20080528"},{"v":"0.83"},{"v":"aseldawy"}]},{"c":[{"v":"20080526"},{"v":"4.50"},{"v":"mostafaali"}]},{"c":[{"v":"20080526"},{"v":"3.5"},{"v":"melkharashy"}]},{"c":[{"v":"20080529"},{"v":"2.83"},{"v":"adewandaru"}]},{"c":[{"v":"20080531"},{"v":"1.5"},{"v":"adewandaru"}]},{"c":[{"v":"20080525"},{"v":"5.00"},{"v":"melkharashy"}]},{"c":[{"v":"20080528"},{"v":"3.00"},{"v":"adewandaru"}]},{"c":[{"v":"20080527"},{"v":"2.0"},{"v":"adewandaru"}]},{"c":[{"v":"20080525"},{"v":"2.67"},{"v":"adewandaru"}]},{"c":[{"v":"20080528"},{"v":"3.67"},{"v":"melkharashy"}]},{"c":[{"v":"20080530"},{"v":"5.5"},{"v":"adewandaru"}]},{"c":[{"v":"20080531"},{"v":"3.67"},{"v":"melkharashy"}]},{"c":[{"v":"20080527"},{"v":"2.17"},{"v":"melkharashy"}]},{"c":[{"v":"20080526"},{"v":"8.01"},{"v":"adewandaru"}]},{"c":[{"v":"20080529"},{"v":"0.67"},{"v":"melkharashy"}]}]}}
|
data/test/workdiary.json
ADDED
@@ -0,0 +1,2 @@
|
|
1
|
+
{"server_time":"1263126093","auth_user":{"first_name":"Ahmed El-Dawy","last_name":".","uid":"aseldawy","mail":"aseldawy@odesk.com","messenger_id":"aseldawy","messenger_type":"yahoo","timezone":"Africa\/Cairo","timezone_offset":"7200"},"snapshots":{"user":{"timezone":"Africa\/Cairo","uid":"aseldawy","timezone_offset":"7200","last_name":".","mail":"aseldawy@odesk.com","creation_time":"1207503924","archiving_time":"1262563200","first_name":"Ahmed El-Dawy"},"snapshot":[{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249372200","keyboard_events_count":"418","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249372678","billing_status":"billed.active","active_window_title":"sadfas","memo":"do nothing","activity":"5","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249372678.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249378200","keyboard_events_count":"431","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249378442","billing_status":"billed.active","active_window_title":"RadRails - Aptana Studio Community Edition ","memo":"do nothing","activity":"2","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249378442.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249378800","keyboard_events_count":"1018","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249379235","billing_status":"billed.active","active_window_title":"Google - Mozilla Firefox","memo":"do nothing","activity":"3","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249379235.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249379400","keyboard_events_count":"1015","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249379749","billing_status":"billed.active","active_window_title":"RadRails - 20090804093424_add_license_quota.rb - Aptana Studio Community Edition ","memo":"do nothing","activity":"7","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249379749.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249380000","keyboard_events_count":"665","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249380394","billing_status":"billed.active","active_window_title":"RadRails - page.rb - Aptana Studio Community Edition ","memo":"do nothing","activity":"7","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249380394.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249382400","keyboard_events_count":"691","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249382513","billing_status":"billed.active","active_window_title":"RadRails - page_test.rb - Aptana Studio Community Edition ","memo":"do nothing","activity":"1","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249382513.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249384200","keyboard_events_count":"536","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249384799","billing_status":"billed.active","active_window_title":"RadRails - page.rb - Aptana Studio Community Edition ","memo":"do nothing","activity":"5","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249384799.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249384800","keyboard_events_count":"1021","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249385243","billing_status":"billed.active","active_window_title":"aseldawy@aseldawy-laptop: ~\/Aptana_Studio\/planswift_backend","memo":"do nothing","activity":"10","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249385243.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249385400","keyboard_events_count":"380","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249385999","billing_status":"billed.active","active_window_title":"RadRails - fixtures.rb - Aptana Studio Community Edition ","memo":"do nothing","activity":"9","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249385999.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249386000","keyboard_events_count":"544","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249386477","billing_status":"billed.active","active_window_title":"Mohamed Amir","memo":"do nothing","activity":"8","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249386477.json"},{"mouse_events_count":"1","company_id":"bebebobo","cell_time":"1249386600","keyboard_events_count":"348","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249387084","billing_status":"billed.active","active_window_title":"RadRails - fixtures.rb - Aptana Studio Community Edition ","memo":"do nothing","activity":"7","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249387084.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249387200","keyboard_events_count":"598","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249387654","billing_status":"billed.active","active_window_title":"aseldawy@aseldawy-laptop: ~\/Aptana_Studio\/planswift_backend","memo":"do nothing","activity":"7","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249387654.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249387800","keyboard_events_count":"950","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249388254","billing_status":"billed.active","active_window_title":"Commit ","memo":"do nothing","activity":"10","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249388254.json"},{"mouse_events_count":"0","company_id":"bebebobo","cell_time":"1249388400","keyboard_events_count":"873","uid":"428268b0-45b1-4a54-83e0-4ec381b39cc0","timezone":"","status":"NORMAL","time":"1249388796","billing_status":"billed.active","active_window_title":"root@domU-12-31-38-00-3C-E3: \/var\/www\/planswift","memo":"do nothing","activity":"7","computer_name":"some computer","user_id":"aseldawy","client_version":"Linux\/1.3.4","snapshot_api":"\/api\/team\/v1\/snapshots\/bebebobo\/aseldawy\/1249388796.json"}],"teamroom_api":"\/api\/team\/v1\/teamrooms\/bebebobo.json"}}
|
2
|
+
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_desk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ahmed ElDawy
|
@@ -38,15 +38,19 @@ files:
|
|
38
38
|
- VERSION
|
39
39
|
- lib/ruby_desk.rb
|
40
40
|
- lib/ruby_desk/connector.rb
|
41
|
-
- lib/ruby_desk/developer_skill.rb
|
42
41
|
- lib/ruby_desk/odesk_entity.rb
|
43
42
|
- lib/ruby_desk/provider.rb
|
44
43
|
- lib/ruby_desk/snapshot.rb
|
45
44
|
- lib/ruby_desk/team_room.rb
|
45
|
+
- lib/ruby_desk/time_report.rb
|
46
46
|
- test/helper.rb
|
47
47
|
- test/profile.json
|
48
48
|
- test/providers.json
|
49
|
+
- test/snapshot.json
|
50
|
+
- test/teamrooms.json
|
49
51
|
- test/test_ruby_desk.rb
|
52
|
+
- test/timereport.json
|
53
|
+
- test/workdiary.json
|
50
54
|
has_rdoc: true
|
51
55
|
homepage: http://github.com/aseldawy/ruby_desk
|
52
56
|
licenses: []
|