togglv8-tfl 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/togglv8.rb ADDED
@@ -0,0 +1,13 @@
1
+ require_relative 'togglv8/version'
2
+
3
+ require_relative 'togglv8/connection'
4
+
5
+ require_relative 'togglv8/togglv8'
6
+ require_relative 'reportsv2'
7
+
8
+ # :mode => :compat will convert symbols to strings
9
+ Oj.default_options = { :mode => :compat }
10
+
11
+ module TogglV8
12
+ NAME = "TogglV8 v#{TogglV8::VERSION}"
13
+ end
@@ -0,0 +1,37 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Clients
7
+ #
8
+ # name : The name of the client (string, required, unique in workspace)
9
+ # wid : workspace ID, where the client will be used (integer, required)
10
+ # notes : Notes for the client (string, not required)
11
+ # hrate : The hourly rate for this client (float, not required, available only for pro workspaces)
12
+ # cur : The name of the client's currency (string, not required, available only for pro workspaces)
13
+ # at : timestamp that is sent in the response, indicates the time client was last updated
14
+
15
+ def create_client(params)
16
+ requireParams(params, ['name', 'wid'])
17
+ post "clients", { 'client' => params }
18
+ end
19
+
20
+ def get_client(client_id)
21
+ get "clients/#{client_id}"
22
+ end
23
+
24
+ def update_client(client_id, params)
25
+ put "clients/#{client_id}", { 'client' => params }
26
+ end
27
+
28
+ def delete_client(client_id)
29
+ delete "clients/#{client_id}"
30
+ end
31
+
32
+ def get_client_projects(client_id, params={})
33
+ active = params.has_key?('active') ? "?active=#{params['active']}" : ""
34
+ get "clients/#{client_id}/projects#{active}"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,99 @@
1
+ require 'faraday'
2
+ require 'oj'
3
+
4
+ require_relative '../logging'
5
+
6
+ module TogglV8
7
+ module Connection
8
+ include Logging
9
+
10
+ DELAY_SEC = 1
11
+ MAX_RETRIES = 3
12
+
13
+ API_TOKEN = 'api_token'
14
+ TOGGL_FILE = '.toggl'
15
+
16
+ def self.open(username=nil, password=API_TOKEN, url=nil, opts={})
17
+ raise 'Missing URL' if url.nil?
18
+
19
+ Faraday.new(:url => url, :ssl => {:verify => true}) do |faraday|
20
+ faraday.request :url_encoded
21
+ faraday.response :logger, Logger.new('faraday.log') if opts[:log]
22
+ faraday.adapter Faraday.default_adapter
23
+ faraday.headers = { "Content-Type" => "application/json" }
24
+ faraday.basic_auth username, password
25
+ end
26
+ end
27
+
28
+ def requireParams(params, fields=[])
29
+ raise ArgumentError, 'params is not a Hash' unless params.is_a? Hash
30
+ return if fields.empty?
31
+ errors = []
32
+ for f in fields
33
+ errors.push("params[#{f}] is required") unless params.has_key?(f)
34
+ end
35
+ raise ArgumentError, errors.join(', ') if !errors.empty?
36
+ end
37
+
38
+ def _call_api(procs)
39
+ # logger.debug(procs[:debug_output].call)
40
+ full_resp = nil
41
+ i = 0
42
+ loop do
43
+ i += 1
44
+ full_resp = procs[:api_call].call
45
+ logger.ap(full_resp.env, :debug)
46
+ break if full_resp.status != 429 || i >= MAX_RETRIES
47
+ sleep(DELAY_SEC)
48
+ end
49
+
50
+ raise full_resp.headers['warning'] if full_resp.headers['warning']
51
+ raise "HTTP Status: #{full_resp.status}" unless full_resp.success?
52
+ return {} if full_resp.body.nil? || full_resp.body == 'null'
53
+
54
+ full_resp
55
+ end
56
+
57
+ def get(resource, params={}, whole_body = false)
58
+ query_params = params.map { |k,v| "#{k}=#{v}" }.join('&')
59
+ resource += "?#{query_params}" unless query_params.empty?
60
+ resource.gsub!('+', '%2B')
61
+ full_resp = _call_api(debug_output: lambda { "GET #{resource}" },
62
+ api_call: lambda { self.conn.get(resource) } )
63
+ return {} if full_resp == {}
64
+ begin
65
+ resp = Oj.load(full_resp.body)
66
+ return resp['data'] if resp.respond_to?(:has_key?) && resp.has_key?('data') && whole_body == false
67
+ return resp
68
+ rescue Oj::ParseError
69
+ return full_resp.body
70
+ end
71
+ end
72
+
73
+ def post(resource, data='')
74
+ resource.gsub!('+', '%2B')
75
+ full_resp = _call_api(debug_output: lambda { "POST #{resource} / #{data}" },
76
+ api_call: lambda { self.conn.post(resource, Oj.dump(data)) } )
77
+ return {} if full_resp == {}
78
+ resp = Oj.load(full_resp.body)
79
+ resp['data']
80
+ end
81
+
82
+ def put(resource, data='')
83
+ resource.gsub!('+', '%2B')
84
+ full_resp = _call_api(debug_output: lambda { "PUT #{resource} / #{data}" },
85
+ api_call: lambda { self.conn.put(resource, Oj.dump(data)) } )
86
+ return {} if full_resp == {}
87
+ resp = Oj.load(full_resp.body)
88
+ resp['data']
89
+ end
90
+
91
+ def delete(resource)
92
+ resource.gsub!('+', '%2B')
93
+ full_resp = _call_api(debug_output: lambda { "DELETE #{resource}" },
94
+ api_call: lambda { self.conn.delete(resource) } )
95
+ return {} if full_resp == {}
96
+ full_resp.body
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,14 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Dashboard
7
+ #
8
+ # See https://github.com/toggl/toggl_api_docs/blob/master/chapters/dashboard.md
9
+
10
+ def dashboard(workspace_id)
11
+ get "dashboard/#{workspace_id}"
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Project Users
7
+ #
8
+ # pid : project ID (integer, required)
9
+ # uid : user ID, who is added to the project (integer, required)
10
+ # wid : workspace ID, where the project belongs to (integer, not-required, project's workspace id is used)
11
+ # manager : admin rights for this project (boolean, default false)
12
+ # rate : hourly rate for the project user (float, not-required, only for pro workspaces) in the currency of the project's client or in workspace default currency.
13
+ # at : timestamp that is sent in the response, indicates when the project user was last updated
14
+ # -- Additional fields --
15
+ # fullname : full name of the user, who is added to the project
16
+
17
+ def create_project_user(params)
18
+ requireParams(params, ['pid', 'uid'])
19
+ params[:fields] = "fullname" # for simplicity, always request fullname field
20
+ post "project_users", { 'project_user' => params }
21
+ end
22
+
23
+ def update_project_user(project_user_id, params)
24
+ params[:fields] = "fullname" # for simplicity, always request fullname field
25
+ put "project_users/#{project_user_id}", { 'project_user' => params }
26
+ end
27
+
28
+ def delete_project_user(project_user_id)
29
+ delete "project_users/#{project_user_id}"
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,112 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Projects
7
+ #
8
+ # See Toggl {Projects}[https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md]
9
+ #
10
+ # name : The name of the project
11
+ # (string, *required*, unique for client and workspace)
12
+ # wid : workspace ID, where the project will be saved
13
+ # (integer, *required*)
14
+ # cid : client ID
15
+ # (integer, not required)
16
+ # active : whether the project is archived or not
17
+ # (boolean, by default true)
18
+ # is_private : whether project is accessible for only project users or for all workspace users
19
+ # (boolean, default true)
20
+ # template : whether the project can be used as a template
21
+ # (boolean, not required)
22
+ # template_id : id of the template project used on current project's creation
23
+ # billable : whether the project is billable or not
24
+ # (boolean, default true, available only for pro workspaces)
25
+ # auto_estimates : whether the estimated hours is calculated based on task estimations or is fixed manually
26
+ # (boolean, default false, not required, premium functionality)
27
+ # estimated_hours : if auto_estimates is true then the sum of task estimations is returned, otherwise user inserted hours
28
+ # (integer, not required, premium functionality)
29
+ # at : timestamp that is sent in the response for PUT, indicates the time task was last updated
30
+ # color : id of the color selected for the project
31
+ # rate : hourly rate of the project
32
+ # (float, not required, premium functionality)
33
+ # created_at : timestamp indicating when the project was created (UTC time), read-only
34
+ # ---------
35
+
36
+ ##
37
+ # :category: Projects
38
+ #
39
+ # Public: Create a new project
40
+ #
41
+ # params - The Hash used to create the project (default: {})
42
+ # :name - The name of the project (string, required, unique for client and workspace)
43
+ # :wid - workspace ID, where the project will be saved (integer, required)
44
+ # :cid - client ID (integer, not required)
45
+ # :active - whether the project is archived or not (boolean, by default true)
46
+ # :is_private - whether project is accessible for only project users or for all workspace users (boolean, default true)
47
+ # :template - whether the project can be used as a template (boolean, not required)
48
+ # :template_id - id of the template project used on current project's creation
49
+ # :billable - whether the project is billable or not (boolean, default true, available only for pro workspaces)
50
+ # :auto_estimates - whether the estimated hours is calculated based on task estimations or is fixed manually (boolean, default false, not required, premium functionality)
51
+ # :estimated_hours - if auto_estimates is true then the sum of task estimations is returned, otherwise user inserted hours (integer, not required, premium functionality)
52
+ # :at - timestamp that is sent in the response for PUT, indicates the time task was last updated
53
+ # :color - id of the color selected for the project
54
+ # :rate - hourly rate of the project (float, not required, premium functionality)
55
+ # :created_at - timestamp indicating when the project was created (UTC time), read-only
56
+ #
57
+ # Examples
58
+ #
59
+ # toggl.create_project({ :name => 'My project', :wid => 1060392 })
60
+ # => {"id"=>10918774,
61
+ # "wid"=>1060392,
62
+ # "name"=>"project5",
63
+ # "billable"=>false,
64
+ # "is_private"=>true,
65
+ # "active"=>true,
66
+ # "template"=>false,
67
+ # "at"=>"2015-08-18T10:03:51+00:00",
68
+ # "color"=>"5",
69
+ # "auto_estimates"=>false}
70
+ #
71
+ # Returns a +Hash+ representing the newly created Project.
72
+ #
73
+ # See Toggl {Create Project}[https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#create-project]
74
+ def create_project(params)
75
+ requireParams(params, ['name', 'wid'])
76
+ post "projects", { 'project' => params }
77
+ end
78
+
79
+ # [Get project data](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-project-data)
80
+ def get_project(project_id)
81
+ get "projects/#{project_id}"
82
+ end
83
+
84
+ # [Update project data](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#update-project-data)
85
+ def update_project(project_id, params)
86
+ put "projects/#{project_id}", { 'project' => params }
87
+ end
88
+
89
+ # [Delete a project](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#delete-a-project)
90
+ def delete_project(project_id)
91
+ delete "projects/#{project_id}"
92
+ end
93
+
94
+ # [Get project users](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-project-users)
95
+ def get_project_users(project_id)
96
+ get "projects/#{project_id}/project_users"
97
+ end
98
+
99
+ # [Get project tasks](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-project-tasks)
100
+ def get_project_tasks(project_id)
101
+ get "projects/#{project_id}/tasks"
102
+ end
103
+
104
+ # [Get workspace projects](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#get-workspace-projects)
105
+
106
+ # [Delete multiple projects](https://github.com/toggl/toggl_api_docs/blob/master/chapters/projects.md#delete-multiple-projects)
107
+ def delete_projects(project_ids)
108
+ return if project_ids.nil?
109
+ delete "projects/#{project_ids.join(',')}"
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,25 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Tags
7
+ #
8
+ # name : The name of the tag (string, required, unique in workspace)
9
+ # wid : workspace ID, where the tag will be used (integer, required)
10
+
11
+ def create_tag(params)
12
+ requireParams(params, ['name', 'wid'])
13
+ post "tags", { 'tag' => params }
14
+ end
15
+
16
+ # ex: update_tag(12345, { :name => "same tame game" })
17
+ def update_tag(tag_id, params)
18
+ put "tags/#{tag_id}", { 'tag' => params }
19
+ end
20
+
21
+ def delete_tag(tag_id)
22
+ delete "tags/#{tag_id}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,55 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Tasks
7
+ #
8
+ # NOTE: Tasks are available only for pro workspaces.
9
+ #
10
+ # name : The name of the task (string, required, unique in project)
11
+ # pid : project ID for the task (integer, required)
12
+ # wid : workspace ID, where the task will be saved
13
+ # (integer, project's workspace id is used when not supplied)
14
+ # uid : user ID, to whom the task is assigned to (integer, not required)
15
+ # estimated_seconds : estimated duration of task in seconds (integer, not required)
16
+ # active : whether the task is done or not (boolean, by default true)
17
+ # at : timestamp that is sent in the response for PUT, indicates the time task was last updated
18
+ # -- Additional fields --
19
+ # done_seconds : duration (in seconds) of all the time entries registered for this task
20
+ # uname : full name of the person to whom the task is assigned to
21
+
22
+ def create_task(params)
23
+ requireParams(params, ['name', 'pid'])
24
+ post "tasks", { 'task' => params }
25
+ end
26
+
27
+ def get_task(task_id)
28
+ get "tasks/#{task_id}"
29
+ end
30
+
31
+ # ex: update_task(1894675, { :active => true, :estimated_seconds => 4500, :fields => "done_seconds,uname"})
32
+ def update_task(task_id, params)
33
+ put "tasks/#{task_id}", { 'task' => params }
34
+ end
35
+
36
+ def delete_task(task_id)
37
+ delete "tasks/#{task_id}"
38
+ end
39
+
40
+ # ------------ #
41
+ # Mass Actions #
42
+ # ------------ #
43
+
44
+ def update_tasks(task_ids, params)
45
+ return if task_ids.nil?
46
+ put "tasks/#{task_ids.join(',')}", { 'task' => params }
47
+ end
48
+
49
+ def delete_tasks(task_ids)
50
+ return if task_ids.nil?
51
+ delete "tasks/#{task_ids.join(',')}"
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,93 @@
1
+ module TogglV8
2
+ class API
3
+
4
+ ##
5
+ # ---------
6
+ # :section: Time Entries
7
+ #
8
+ # https://github.com/toggl/toggl_api_docs/blob/master/chapters/time_entries.md
9
+ #
10
+ # description : (string, strongly suggested to be used)
11
+ # wid : workspace ID (integer, required if pid or tid not supplied)
12
+ # pid : project ID (integer, not required)
13
+ # tid : task ID (integer, not required)
14
+ # billable : (boolean, not required, default false, available for pro workspaces)
15
+ # start : time entry start time (string, required, ISO 8601 date and time)
16
+ # stop : time entry stop time (string, not required, ISO 8601 date and time)
17
+ # duration : time entry duration in seconds. If the time entry is currently running,
18
+ # the duration attribute contains a negative value,
19
+ # denoting the start of the time entry in seconds since epoch (Jan 1 1970).
20
+ # The correct duration can be calculated as current_time + duration,
21
+ # where current_time is the current time in seconds since epoch. (integer, required)
22
+ # created_with : the name of your client app (string, required)
23
+ # tags : a list of tag names (array of strings, not required)
24
+ # duronly : should Toggl show the start and stop time of this time entry? (boolean, not required)
25
+ # at : timestamp that is sent in the response, indicates the time item was last updated
26
+
27
+ def create_time_entry(params)
28
+ params['created_with'] = TogglV8::NAME unless params.has_key?('created_with')
29
+ requireParams(params, ['start', 'duration', 'created_with'])
30
+ if !params.has_key?('wid') and !params.has_key?('pid') and !params.has_key?('tid') then
31
+ raise ArgumentError, "one of params['wid'], params['pid'], params['tid'] is required"
32
+ end
33
+ post "time_entries", { 'time_entry' => params }
34
+ end
35
+
36
+ def start_time_entry(params)
37
+ params['created_with'] = TogglV8::NAME unless params.has_key?('created_with')
38
+ if !params.has_key?('wid') and !params.has_key?('pid') and !params.has_key?('tid') then
39
+ raise ArgumentError, "one of params['wid'], params['pid'], params['tid'] is required"
40
+ end
41
+ post "time_entries/start", { 'time_entry' => params }
42
+ end
43
+
44
+ def stop_time_entry(time_entry_id)
45
+ put "time_entries/#{time_entry_id}/stop", {}
46
+ end
47
+
48
+ def get_time_entry(time_entry_id)
49
+ get "time_entries/#{time_entry_id}"
50
+ end
51
+
52
+ def get_current_time_entry
53
+ get "time_entries/current"
54
+ end
55
+
56
+ def update_time_entry(time_entry_id, params)
57
+ put "time_entries/#{time_entry_id}", { 'time_entry' => params }
58
+ end
59
+
60
+ def delete_time_entry(time_entry_id)
61
+ delete "time_entries/#{time_entry_id}"
62
+ end
63
+
64
+ def iso8601(timestamp)
65
+ return nil if timestamp.nil?
66
+ if timestamp.is_a?(DateTime) or timestamp.is_a?(Date)
67
+ formatted_ts = timestamp.iso8601
68
+ elsif timestamp.is_a?(String)
69
+ formatted_ts = DateTime.parse(timestamp).iso8601
70
+ else
71
+ raise ArgumentError, "Can't convert #{timestamp.class} to ISO-8601 Date/Time"
72
+ end
73
+ return formatted_ts.sub('+00:00', 'Z')
74
+ end
75
+
76
+ def get_time_entries(dates = {})
77
+ start_date = dates[:start_date]
78
+ end_date = dates[:end_date]
79
+ params = []
80
+ params.push("start_date=#{iso8601(start_date)}") unless start_date.nil?
81
+ params.push("end_date=#{iso8601(end_date)}") unless end_date.nil?
82
+ get "time_entries%s" % [params.empty? ? "" : "?#{params.join('&')}"]
83
+ end
84
+
85
+ # Example params: {'tags' =>['billed','productive'], 'tag_action' => 'add'}
86
+ # tag_action can be 'add' or 'remove'
87
+ def update_time_entries_tags(time_entry_ids, params)
88
+ return if time_entry_ids.nil?
89
+ requireParams(params, ['tags', 'tag_action'])
90
+ put "time_entries/#{time_entry_ids.join(',')}", { 'time_entry' => params }
91
+ end
92
+ end
93
+ end