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 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
- Gets the URL that will ask the user to authenticate your application
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.2.0
1
+ 0.3.0
@@ -5,12 +5,15 @@ require 'net/https'
5
5
 
6
6
  module RubyDesk
7
7
  class Connector
8
- ODESK_API_URL = "www.odesk.com/api/"
9
- ODESK_AUTH_URL = "www.odesk.com/services/api/auth/"
10
- DEFAULT_OPTIONS = {:secure=>true, :sign=>true, :format=>'xml'}
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
- md5 = Digest::MD5.hexdigest(concatenated)
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
- def prepare_api_call(path, params = {}, options = {})
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[:api_sig] = sign(params) if options[:sign]
47
- {:url=>"http#{options[:secure]? 's' : ''}://" + ODESK_API_URL + path + "." + options[:format],
48
- :params=> params, :method=>options[:method]}
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, params = {}, options = {})
76
- api_call = prepare_api_call(path, params, options)
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
- params = {:api_key=>@api_key}
83
- params[:api_sig] = sign(params)
84
- "https://"+ODESK_AUTH_URL+"?"+params.to_a.map{|pair| pair.join '='}.join('&')
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
- "https://"+ODESK_AUTH_URL
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', {:frob=>@frob, :api_key=>@api_key}, :method=>:post, :format=>'json'
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', {:api_key=>@api_key}, :method=>:post, :format=>'json'
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', {:api_key=>@api_key, :api_token=>@api_token}, :method=>:get, :format=>'json'
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', {:api_key=>@api_key, :api_token=>@api_token}, :method=>:delete, :format=>'json'
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
+
@@ -1,91 +1,7 @@
1
1
  class RubyDesk::Provider < RubyDesk::OdeskEntity
2
2
  class << self
3
3
  def all_categories
4
- text = <<-CATEGORIES
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 'profiles/v1/search/providers', {:api_token=>@api_token, :api_key=>@api_key}, :method=>:get, :format=>'json'
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("profiles/v1/providers/#{id}" + (brief ? "/brief" : ""),
174
- {:api_token=>@api_token, :api_key=>@api_key}, :method=>:get, :format=>'json')
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
+
@@ -8,4 +8,38 @@ class RubyDesk::Snapshot < RubyDesk::OdeskEntity
8
8
 
9
9
  attribute :user, :class=>RubyDesk::User
10
10
 
11
- end
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
+
@@ -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', {:api_token=>@api_token, :api_key=>@api_key}, :method=>:get, :format=>'json'
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}", {:api_token=>@api_token, :api_key=>@api_key, :online=>online}, :method=>:get, :format=>'json'
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
- end
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
- :cmp_percentile, :cmp_name, :cmp_recno, :cmp_score, :cmp_id)
20
- Skill = define_simple_class(:skl_level_num, :skl_ref, :skl_name, :skl_last_used,
21
- :skl_recno, :skl_level, :skl_comment, :skl_year_exp)
22
- Agency = define_simple_class(:ag_adj_score, :ag_logo, :ag_name, :ag_recent_hours, :ag_tot_feedback,
23
- :ag_recno, :ciphertext, :ag_adj_score_recent, :ag_total_hours)
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
- :ts_provider, :ts_score, :ts_percentile, :ts_curl_name, :ts_ref,
26
- :ts_is_certification, :ts_id, :ts_pass, :ts_seo_link)
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
- :exp_title, :exp_company, :exp_to)
30
+ :exp_title, :exp_company, :exp_to)
30
31
  Candidacy = define_simple_class(:permalink, :dev_recno, :opening_ciphertext,
31
- :rel_requested_assignment, :created_type, :developer_ciphertext,
32
- :ciphertext, :create_date, :profile_seo_link, :Agencyref, :agency_ciphertext,
33
- :op_title, :record_id, :job_profile_access)
34
- Assignment = define_simple_class(:as_type, :as_blended_cost_rate, :as_total_hours,
35
- :as_to, :as_from, :as_client, :as_opening_access, :as_agency_name, :as_rate,
36
- :as_blended_charge_rate, :as_amount, :as_total_charge, :as_opening_recno,
37
- :as_description, :as_opening_title, :as_status, :as_job_type,
38
- :as_ciphertext_opening_recno, :as_total_cost)
39
- Certificate = define_simple_class(:cmp_url, :cmp_when, :cmp_provider, :cmp_ref,
40
- :cmp_percentile, :cmp_name, :cmp_recno, :cmp_score, :cmp_id)
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
- :ed_recno, :ed_comment, :ed_from)
43
- OtherExperience = define_simple_class(:exp_recno, :exp_description, :exp_subject)
44
- Trend = define_simple_class(:trend_word, :trend_url, :trend_recno, :trend_skill)
45
- PortfolioItem = define_simple_class(:pi_recno, :pi_description, :pi_attachment,
46
- :pi_completed, :pi_thumbnail, :pi_title, :pi_category, :pi_url, :pi_image)
47
- User = define_simple_class(:messenger_id, :timezone, :uid, :timezone_offset, :last_name,
48
- :mail, :creation_time, :first_name)
49
- # = define_simple_class()
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
+
@@ -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
+
@@ -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
+
@@ -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"}]}]}}
@@ -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.2.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: []
@@ -1,3 +0,0 @@
1
- class RubyDesk::DeveloperSkill < RubyDesk::OdeskEntity
2
- attributes :order, :description, :avg_category_score_recent, :avg_category_score, :label
3
- end