diff --git a/fixtures/client_create.json b/fixtures/client_create.json new file mode 100644 index 0000000..a5b4efc --- /dev/null +++ b/fixtures/client_create.json @@ -0,0 +1,7 @@ +{"data": { + "id": 1239455, + "wid": 777, + "name": "Very Big Company", + "at": "2013-02-26T08:45:28+00:00" + } +} diff --git a/fixtures/client_get.json b/fixtures/client_get.json new file mode 100644 index 0000000..c80c0db --- /dev/null +++ b/fixtures/client_get.json @@ -0,0 +1,11 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"Very Big Company", + "at":"2013-02-26T08:45:28+00:00", + "notes": "Contact: John Jacob Jingleheimer Schmidt", + "hrate": 12, + "cur": "AUD" + } +} diff --git a/fixtures/client_projects_get.json b/fixtures/client_projects_get.json new file mode 100644 index 0000000..1c46b57 --- /dev/null +++ b/fixtures/client_projects_get.json @@ -0,0 +1,21 @@ +[ + { + "id":909, + "wid":777, + "cid":987, + "name":"Very lucrative project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T09:15:18+00:00" + },{ + "id":32143, + "wid":777, + "cid":987, + "name":"Factory server infrastructure", + "billable":true, + "is_private":true, + "active":true, + "at":"2013-03-06T09:16:06+00:00" + } +] diff --git a/fixtures/client_update.json b/fixtures/client_update.json new file mode 100644 index 0000000..ac322dc --- /dev/null +++ b/fixtures/client_update.json @@ -0,0 +1,9 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"Very Big Company", + "notes":"something about the client", + "at":"2013-02-26T08:55:28+00:00" + } +} diff --git a/fixtures/clients_get.json b/fixtures/clients_get.json new file mode 100644 index 0000000..abfc49a --- /dev/null +++ b/fixtures/clients_get.json @@ -0,0 +1,15 @@ +[ + { + "id":1239455, + "wid":777, + "name":"Very Big Company", + "notes":"something about the client", + "at":"2013-02-26T08:55:28+00:00" + }, { + "id":1239456, + "wid":777, + "name":"Small Startup", + "notes":"Really cool people", + "at":"2013-03-26T08:55:28+00:00" + } +] diff --git a/fixtures/dashboard.json b/fixtures/dashboard.json new file mode 100644 index 0000000..513689f --- /dev/null +++ b/fixtures/dashboard.json @@ -0,0 +1,91 @@ +{ + "most_active_user":[ + { + "user_id":426060, + "duration":97652 + }, + { + "user_id":682638, + "duration":30600 + }, + { + "user_id":426054, + "duration":23400 + }, + { + "user_id":682650, + "duration":21600 + }, + { + "user_id":426055, + "duration":7506 + } + ], + "activity":[ + { + "user_id":6822650, + "project_id":34654059, + "duration":-1392018288, + "description":"new design" + }, + { + "user_id":4260544, + "project_id":30349753, + "duration":-1392019190, + "description":"backend" + }, + { + "user_id":426055, + "project_id":3689864, + "duration":-1392021417, + "description":"writing post" + }, + { + "user_id":6822650, + "project_id":36898655, + "duration":10800, + "description":"design", + "stop":"2014-02-14T14:30:00+00:00" + }, + { + "user_id":35224123, + "project_id":0, + "duration":65573, + "stop":"2014-02-13T08:12:06+00:00" + }, + { + "user_id":35224123, + "project_id":3689865, + "duration":3600, + "stop":"2014-02-10T15:30:00+00:00" + }, + { + "user_id":35224123, + "project_id":3535357, + "duration":10800, + "description":"backend", + "stop":"2014-02-10T14:30:00+00:00" + }, + { + "user_id":6822638, + "project_id":3534557, + "duration":12600, + "description":"call", + "stop":"2014-02-10T12:22:00+00:00" + }, + { + "user_id":35224123, + "project_id":303258, + "duration":5280, + "description":"research for presentation", + "stop":"2014-02-10T11:58:00+00:00" + }, + { + "user_id":35224123, + "project_id":346409, + "duration":7200, + "description":"Meeting", + "stop":"2014-02-10T10:00:00+00:00" + } + ] +} diff --git a/fixtures/project_create.json b/fixtures/project_create.json new file mode 100644 index 0000000..f9951cf --- /dev/null +++ b/fixtures/project_create.json @@ -0,0 +1,14 @@ +{ + "data": { + "id":193838628, + "wid":777, + "cid":123397, + "name":"An awesome project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T12:15:37+00:00", + "template_id":10237, + "color": "5" + } +} diff --git a/fixtures/project_get.json b/fixtures/project_get.json new file mode 100644 index 0000000..8c44d6a --- /dev/null +++ b/fixtures/project_get.json @@ -0,0 +1,14 @@ +{ + "data": { + "id":193838628, + "wid":777, + "cid":123397, + "name":"An awesome project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T12:15:37+00:00", + "template":true, + "color": "5" + } +} diff --git a/fixtures/project_projectusers_get.json b/fixtures/project_projectusers_get.json new file mode 100644 index 0000000..f3872cc --- /dev/null +++ b/fixtures/project_projectusers_get.json @@ -0,0 +1,18 @@ +[ + { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":true, + "rate":4 + }, + { + "id":4692193, + "pid":777, + "uid":125, + "wid":99, + "manager":false, + "rate":4 + } +] diff --git a/fixtures/project_tasks_get.json b/fixtures/project_tasks_get.json new file mode 100644 index 0000000..b637d5b --- /dev/null +++ b/fixtures/project_tasks_get.json @@ -0,0 +1,19 @@ +[ + { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600 + }, { + "name":"Another task", + "id":1335076911, + "uid": 12309, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00" + } +] diff --git a/fixtures/project_update.json b/fixtures/project_update.json new file mode 100644 index 0000000..c257dbf --- /dev/null +++ b/fixtures/project_update.json @@ -0,0 +1,13 @@ +{ + "data": { + "id":193838628, + "wid":777, + "cid":123398, + "name":"Changed the name", + "billable":false, + "active":true, + "at":"2013-03-06T12:15:37+00:00", + "template":true, + "color":"6" + } +} diff --git a/fixtures/projectuser_create.json b/fixtures/projectuser_create.json new file mode 100644 index 0000000..0d2da87 --- /dev/null +++ b/fixtures/projectuser_create.json @@ -0,0 +1,10 @@ +{ + "data": { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":true, + "rate":4 + } +} diff --git a/fixtures/projectuser_update.json b/fixtures/projectuser_update.json new file mode 100644 index 0000000..97cbc63 --- /dev/null +++ b/fixtures/projectuser_update.json @@ -0,0 +1,12 @@ +{ + "data": { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":false, + "rate":15, + "fullname":"John Swift", + "at":"2013-03-05T09:21:44+00:00" + } +} diff --git a/fixtures/projectusers_create_multiple.json b/fixtures/projectusers_create_multiple.json new file mode 100644 index 0000000..ed792e2 --- /dev/null +++ b/fixtures/projectusers_create_multiple.json @@ -0,0 +1,26 @@ +{ + "data":[ + { + "id":4692190, + "pid":777, + "uid":1267998, + "wid":99, + "manager":true, + "rate":4, + },{ + "id":4692192, + "pid":777, + "uid":29624, + "wid":99, + "manager":true, + "rate":4, + },{ + "id":4692191, + "pid":777, + "uid":112047, + "wid":99, + "manager":true, + "rate":4, + } + ] +} diff --git a/fixtures/projectusers_get.json b/fixtures/projectusers_get.json new file mode 100644 index 0000000..0d2da87 --- /dev/null +++ b/fixtures/projectusers_get.json @@ -0,0 +1,10 @@ +{ + "data": { + "id":4692190, + "pid":777, + "uid":123, + "wid":99, + "manager":true, + "rate":4 + } +} diff --git a/fixtures/projectusers_update_multiple.json b/fixtures/projectusers_update_multiple.json new file mode 100644 index 0000000..cea6fc2 --- /dev/null +++ b/fixtures/projectusers_update_multiple.json @@ -0,0 +1,29 @@ +{ + "data":[ + { + "id":4692190, + "pid":777, + "uid":1267998, + "wid":99, + "manager":false, + "rate":15, + "at":"2013-03-05T09:20:58+00:00" + },{ + "id":4692192, + "pid":777, + "uid":29624, + "wid":99, + "manager":false, + "rate":15, + "at":"2013-03-05T09:20:58+00:00" + },{ + "id":4692191, + "pid":777, + "uid":112047, + "wid":99, + "manager":false, + "rate":15, + "at":"2013-03-05T09:20:58+00:00" + } + ] +} diff --git a/fixtures/reset_token.json b/fixtures/reset_token.json new file mode 100644 index 0000000..6819458 --- /dev/null +++ b/fixtures/reset_token.json @@ -0,0 +1 @@ +"a0123123b8e43343d614553f95f9192ab9c1" diff --git a/fixtures/signups.json b/fixtures/signups.json new file mode 100644 index 0000000..2901d63 --- /dev/null +++ b/fixtures/signups.json @@ -0,0 +1,19 @@ +{ + "data":{ + "id":599978901, + "api_token":"808lolae4eab897cce9729a53642124effe", + "default_wid":983493, + "email":"test.user@toggl.com", + "fullname":"Test User", + "jquery_timeofday_format":"", + "jquery_date_format":"", + "timeofday_format":"", + "date_format":"", + "store_start_and_stop_time":false, + "beginning_of_week":0, + "sidebar_piechart":false, + "timeline_experiment":false, + "new_blog_post":{}, + "invitation":{} + } +} diff --git a/fixtures/tag_create.json b/fixtures/tag_create.json new file mode 100644 index 0000000..a0cbc84 --- /dev/null +++ b/fixtures/tag_create.json @@ -0,0 +1,7 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"billed" + } +} diff --git a/fixtures/tag_update.json b/fixtures/tag_update.json new file mode 100644 index 0000000..4687967 --- /dev/null +++ b/fixtures/tag_update.json @@ -0,0 +1,7 @@ +{ + "data": { + "id":1239455, + "wid":777, + "name":"not billed" + } +} diff --git a/fixtures/task_create.json b/fixtures/task_create.json new file mode 100644 index 0000000..7694ba6 --- /dev/null +++ b/fixtures/task_create.json @@ -0,0 +1,10 @@ +{ + "data": { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":true, + "estimated_seconds":0 + } +} diff --git a/fixtures/task_get.json b/fixtures/task_get.json new file mode 100644 index 0000000..7694ba6 --- /dev/null +++ b/fixtures/task_get.json @@ -0,0 +1,10 @@ +{ + "data": { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":true, + "estimated_seconds":0 + } +} diff --git a/fixtures/task_update.json b/fixtures/task_update.json new file mode 100644 index 0000000..086e724 --- /dev/null +++ b/fixtures/task_update.json @@ -0,0 +1,13 @@ +{ + "data": { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600, + "uname": "John Swift", + "done_seconds": 1200 + } +} diff --git a/fixtures/tasks_update_multiple.json b/fixtures/tasks_update_multiple.json new file mode 100644 index 0000000..4bd2fd9 --- /dev/null +++ b/fixtures/tasks_update_multiple.json @@ -0,0 +1,24 @@ +{ + "data": [ + { + "name":"A new task", + "id":1335076912, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600, + "uname": "John Swift", + "done_seconds": 1200 + }, { + "name":"Another task", + "id":1335076911, + "wid":888, + "pid":777, + "active":false, + "at":"2013-02-26T15:09:52+00:00", + "estimated_seconds":3600, + "done_seconds": 10 + } + ] +} diff --git a/fixtures/time_entries_get_in_range.json b/fixtures/time_entries_get_in_range.json new file mode 100644 index 0000000..dfc046c --- /dev/null +++ b/fixtures/time_entries_get_in_range.json @@ -0,0 +1,24 @@ +[ + { + "id":436691234, + "wid":777, + "pid":123, + "billable":true, + "start":"2013-03-11T11:36:00+00:00", + "stop":"2013-03-11T15:36:00+00:00", + "duration":14400, + "description":"Meeting with the client", + "tags":[""], + "at":"2013-03-11T15:36:58+00:00" + },{ + "id":436776436, + "wid":777, + "billable":false, + "start":"2013-03-12T10:32:43+00:00", + "stop":"2013-03-12T14:32:43+00:00", + "duration":18400, + "description":"important work", + "tags":[""], + "at":"2013-03-12T14:32:43+00:00" + } +] diff --git a/fixtures/time_entries_update_multiple.json b/fixtures/time_entries_update_multiple.json new file mode 100644 index 0000000..b2f67eb --- /dev/null +++ b/fixtures/time_entries_update_multiple.json @@ -0,0 +1,29 @@ +{ "data":[ + { + "id":436694100, + "guid":"dfc88541-f3b0-bffe-fa2e-46a09fa97664", + "wid":777, + "pid":20123718, + "billable":true, + "start":"2013-08-01T10:46:00", + "stop":"2013-08-01T11:46:02", + "duration":3602, + "description":"Development", + "tags":["billed","poductive","overhours"], + "duronly":false, + "at":"2013-08-01T12:04:57" + },{ + "id":436694101, + "guid":"ce3c2409-e624-64e2-6742-c623ff284091", + "wid":777, + "billable":false, + "start":"2013-08-01T11:11:00", + "stop":"2013-08-01T11:46:04", + "duration":2104, + "description":"Meeting with the client", + "tags":["billed","poductive","holiday"], + "duronly":false, + "at":"2013-08-01T11:46:08" + } + ] +} diff --git a/fixtures/time_entry_create.json b/fixtures/time_entry_create.json new file mode 100644 index 0000000..99abe80 --- /dev/null +++ b/fixtures/time_entry_create.json @@ -0,0 +1,13 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "duration":1200, + "description":"Meeting with possible clients", + "tags":["billed"] + } +} diff --git a/fixtures/time_entry_current.json b/fixtures/time_entry_current.json new file mode 100644 index 0000000..38f68af --- /dev/null +++ b/fixtures/time_entry_current.json @@ -0,0 +1,12 @@ +{ + "data":{ + "id":436694100, + "wid":777, + "pid":193791, + "billable":false, + "start":"2014-01-30T09:08:04+00:00", + "duration":-1391072884, + "description":"Running time entry", + "at":"2014-01-30T09:08:12+00:00" + } +} diff --git a/fixtures/time_entry_get.json b/fixtures/time_entry_get.json new file mode 100644 index 0000000..2a39979 --- /dev/null +++ b/fixtures/time_entry_get.json @@ -0,0 +1,15 @@ +{ + "data":{ + "id":436694100, + "wid":777, + "pid":193791, + "tid":13350500, + "billable":false, + "start":"2013-02-27T01:24:00+00:00", + "stop":"2013-02-27T07:24:00+00:00", + "duration":21600, + "description":"Some serious work", + "tags":["billed"], + "at":"2013-02-27T13:49:18+00:00" + } +} diff --git a/fixtures/time_entry_start.json b/fixtures/time_entry_start.json new file mode 100644 index 0000000..ed5f984 --- /dev/null +++ b/fixtures/time_entry_start.json @@ -0,0 +1,13 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "duration":-1362470338, + "description":"Meeting with possible clients", + "tags":["billed"] + } +} diff --git a/fixtures/time_entry_stop.json b/fixtures/time_entry_stop.json new file mode 100644 index 0000000..a5c86ed --- /dev/null +++ b/fixtures/time_entry_stop.json @@ -0,0 +1,13 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "duration":60, + "description":"Meeting with possible clients", + "tags":["billed"] + } +} diff --git a/fixtures/time_entry_update.json b/fixtures/time_entry_update.json new file mode 100644 index 0000000..4d4c21b --- /dev/null +++ b/fixtures/time_entry_update.json @@ -0,0 +1,15 @@ +{ + "data": + { + "id":436694100, + "pid":123, + "wid":777, + "billable":false, + "start":"2013-03-05T07:58:58.000Z", + "stop":"2013-03-05T08:58:58.000Z", + "duration":1240, + "description":"Meeting with possible clients", + "billable": true, + "at": "2013-03-05T12:34:50+00:00" + } +} diff --git a/fixtures/user_get.json b/fixtures/user_get.json new file mode 100644 index 0000000..68ffcde --- /dev/null +++ b/fixtures/user_get.json @@ -0,0 +1,62 @@ +{"since":1438961718, +"data":{ + "new_blog_post":{}, + "default_wid":979325, + "beginning_of_week":1, + "at":"2015-07-27T15:34:34+00:00", + "api_token":"fake_token_1", + "timezone":"America/New_York", + "timeofday_format":"h:mm A", + "id":1733991, + "retention":9, + "duration_format":"improved", + "timeline_experiment":true, + "send_timer_notifications":true, + "sidebar_piechart":true, + "last_blog_entry":"http://blog.toggl.com/2015/07/new-pro-feature-custom-rates-for-team-members/", + "timeline_enabled":false, + "send_product_emails":true, + "achievements_enabled":true, + "email":"email@domain.com", + "send_weekly_report":true, + "openid_email":"email@domain.com", + "workspaces":[{ + "profile":0, + "rounding_minutes":0, + "premium":false, + "name":"Firstname Lastname's workspace", + "admin":true, + "rounding":1, + "at":"2015-06-10T19:06:43+00:00", + "default_hourly_rate":0, + "ical_enabled":true, + "only_admins_see_team_dashboard":false, + "only_admins_see_billable_rates":false, + "api_token":"fake_token_2", + "projects_billable_by_default":true, + "subscription":{ + "description":"Free" + }, + "only_admins_may_create_projects":false, + "id":979325, + "default_currency":"USD" + }], + "store_start_and_stop_time":true, + "should_upgrade":true, + "fullname":"Firstname Lastname", + "date_format":"MM/DD/YYYY", + "obm":{ + "nr":0 + }, + "render_timeline":true, + "language":"en_US", + "openid_enabled":true, + "record_timeline":false, + "created_at":"2015-06-10T19:06:43+00:00", + "manual_mode":true, + "image_url":"https://assets.toggl.com/images/profile.png", + "invitation":{}, + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y" + } +} diff --git a/fixtures/user_get_with_related_data.json b/fixtures/user_get_with_related_data.json new file mode 100644 index 0000000..dc1a1e9 --- /dev/null +++ b/fixtures/user_get_with_related_data.json @@ -0,0 +1,85 @@ +{ + "since":1362575771, + "data": { + "id":9000, + "api_token":"1971800d4d82861d8f2c1651fea4d212", + "default_wid":777, + "email":"johnt@swift.com", + "fullname":"John Swift", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y", + "timeofday_format":"h:mm A", + "date_format":"MM/DD/YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":0, + "language":"en_US", + "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", + "sidebar_piechart":false, + "at":"2013-03-06T12:18:42+00:00", + "retention":9, + "record_timeline":true, + "render_timeline":true, + "timeline_enabled":true, + "timeline_experiment":true, + "new_blog_post": + { + "title":"Increasing perceived performance with _.throttle()", + "url":"http://blog.toggl.com/2013/02/increasing-perceived-performance-with-_throttle/?utm_source=rss&utm_medium=rss&utm_campaign=increasing-perceived-performance-with-_throttle" + }, + "time_entries":[ + { + "id":435559433, + "wid":777, + "billable":false, + "start":"2013-03-06T10:08:23+00:00", + "stop":"2013-03-06T14:08:23+00:00", + "duration":14400, + "description":"Best work so far", + "tags":[""], + "at":"2013-03-06T14:08:23+00:00" + } + ], + "projects":[ + { + "id":1230994, + "wid":777, + "name":"Important project", + "billable":false, + "active":false, + "at":"2013-03-06T09:13:31+00:00" + "color":"5" + } + ], + "tags":[ + { + "id":159637, + "wid":188309, + "name":"billable", + "at":"2013-02-21T14:57:46+00:00" + },{ + "id":159654, + "wid":188309, + "name":"important", + "at":"2013-02-22T14:06:17+00:00" + } + ], + "workspaces":[ + { + "id":777, + "name": + "John's WS", + "premium":true, + "at":"2013-03-06T09:00:30+00:00" + } + ], + "clients":[ + { + "id":923476, + "wid":777, + "name":"Best client", + "at":"2013-03-06T09:00:30+00:00" + } + ] + + } +} diff --git a/fixtures/user_update.json b/fixtures/user_update.json new file mode 100644 index 0000000..f3fd162 --- /dev/null +++ b/fixtures/user_update.json @@ -0,0 +1,31 @@ +{ + "data":{ + "id":123123123, + "api_token":"1971800d4d82861d8f2c1651fea4d212", + "default_wid":777, + "email":"john@toggl.com", + "fullname":"John Smith", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"d.m.Y", + "timeofday_format":"h:mm A", + "date_format":"DD.MM.YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":1, + "language":"en_US", + "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", + "sidebar_piechart":false, + "at":"2013-08-12T11:55:58+03:00", + "retention":9, + "record_timeline":true, + "render_timeline":true, + "timeline_enabled":true, + "timeline_experiment":true, + "new_blog_post":{}, + "timezone":"Europe/Tallinn", + "openid_enabled":true, + "send_product_emails":true, + "send_weekly_report":true, + "send_timer_notifications":true, + "invitation":{} + } +} diff --git a/fixtures/workspace_clients.json b/fixtures/workspace_clients.json new file mode 100644 index 0000000..c0cdf73 --- /dev/null +++ b/fixtures/workspace_clients.json @@ -0,0 +1,19 @@ +[ + { + "id":123, + "wid":777, + "name":"Rising Start-Up", + "at":"2013-03-06T09:06:13+00:00", + "notes":"Arrange a discount for them", + "hrate":2, + "cur":"USD" + },{ + "id":987, + "wid":777, + "name":"Big Company Inc", + "at":"2013-03-06T09:05:40+00:00", + "notes":"We had some lovely projects with them", + "hrate":10, + "cur":"EUR" + } +] diff --git a/fixtures/workspace_get.json b/fixtures/workspace_get.json new file mode 100644 index 0000000..5bdac60 --- /dev/null +++ b/fixtures/workspace_get.json @@ -0,0 +1,16 @@ +{ + "data": { + "id":3134975, + "name":"John's personal ws", + "premium":true, + "admin":true, + "default_hourly_rate":150, + "default_currency":"USD", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":false, + "rounding":1, + "rounding_minutes":15, + "at":"2013-08-28T16:22:21+03:00", + "logo_url":"my_logo.png" + } +} diff --git a/fixtures/workspace_invite.json b/fixtures/workspace_invite.json new file mode 100644 index 0000000..109edce --- /dev/null +++ b/fixtures/workspace_invite.json @@ -0,0 +1,12 @@ +{ + "data":{[ + "id":3511220, + "uid":35934278, + "wid":777, + "admin":false, + "active":false, + "email":"jane.swift@toggl.com", + "invite_url":"https://toggl.com/user/accept_invitation?code=ec2876e421234dfasd0fa1c55370d3940" + ]}, + "notifications":["To add more users you have to Upgrade"] +} diff --git a/fixtures/workspace_projects.json b/fixtures/workspace_projects.json new file mode 100644 index 0000000..5fe114b --- /dev/null +++ b/fixtures/workspace_projects.json @@ -0,0 +1,21 @@ +[ + { + "id":909, + "wid":777, + "cid":987, + "name":"Very lucrative project", + "billable":false, + "is_private":true, + "active":true, + "at":"2013-03-06T09:15:18+00:00" + },{ + "id":32123, + "wid":777, + "cid":123, + "name":"Factory server infrastructure", + "billable":true, + "is_private":true, + "active":true, + "at":"2013-03-06T09:16:06+00:00" + } +] diff --git a/fixtures/workspace_tags.json b/fixtures/workspace_tags.json new file mode 100644 index 0000000..81109e9 --- /dev/null +++ b/fixtures/workspace_tags.json @@ -0,0 +1,15 @@ +[ + { + "id":151285, + "wid":777, + "name":"Billed" + },{ + "id":1596623, + "wid":777, + "name":"Invoiced" + },{ + "id":159643, + "wid":777, + "name":"Discarded" + } +] diff --git a/fixtures/workspace_tasks.json b/fixtures/workspace_tasks.json new file mode 100644 index 0000000..50c79df --- /dev/null +++ b/fixtures/workspace_tasks.json @@ -0,0 +1,29 @@ +[ + { + "name":"SWOT", + "id":13512097, + "wid":777, + "pid":32123, + "uid":123123, + "active":true, + "at":"2013-03-06T09:15:51+00:00", + "estimated_seconds":7200 + },{ + "name":"development", + "id":133504498, + "wid":777, + "pid":32123, + "active":true, + "at":"2013-03-06T09:15:59+00:00", + "estimated_seconds":0 + },{ + "name":"analyze SEO", + "id":1335112300, + "wid":777, + "pid":909, + "active":true, + "at":"2013-03-06T09:18:57+00:00", + "estimated_seconds":21600 + } + +] diff --git a/fixtures/workspace_update.json b/fixtures/workspace_update.json new file mode 100644 index 0000000..9b57ffe --- /dev/null +++ b/fixtures/workspace_update.json @@ -0,0 +1,16 @@ +{ + "data": { + "id":3134975, + "name":"John's ws", + "premium":true, + "admin":true, + "default_hourly_rate":50, + "default_currency":"USD", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":true, + "rounding":1, + "rounding_minutes":60, + "at":"2013-08-28T16:22:21+03:00", + "logo_url":"my_logo.png" + } +} diff --git a/fixtures/workspace_users.json b/fixtures/workspace_users.json new file mode 100644 index 0000000..0ba6b85 --- /dev/null +++ b/fixtures/workspace_users.json @@ -0,0 +1,47 @@ +[ + { + "id":123123, + "default_wid":777, + "email":"john@swift.com", + "fullname":"John Swift", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y", + "timeofday_format":"h:mm A", + "date_format":"MM/DD/YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":0, + "language":"en_US", + "image_url":"https://www.toggl.com/system/avatars/123123/small/open-uri20121116-2767-b1qr8l.png", + "sidebar_piechart":false, + "at":"2013-03-06T08:57:12+00:00", + "retention":9, + "record_timeline":true, + "render_timeline":true, + "timeline_enabled":true, + "timeline_experiment":true, + "new_blog_post":{}, + "should_upgrade":true, + "invitation":{} + },{ + "id":321321, + "email":"Happy@worker.com", + "fullname":"Happy Worker", + "jquery_timeofday_format":"h:i A", + "jquery_date_format":"m/d/Y", + "timeofday_format":"h:mm A", + "date_format":"MM/DD/YYYY", + "store_start_and_stop_time":true, + "beginning_of_week":1, + "language":"en_US", + "image_url":"https://www.toggl.com/images/profile.png", + "sidebar_piechart":false, + "at":"2013-03-06T08:46:07+00:00", + "created_at":"2013-03-06T07:52:03+00:00", + "retention":9, + "render_timeline":true, + "timeline_experiment":true, + "new_blog_post":{}, + "should_upgrade":true, + "invitation":{} + } +] diff --git a/fixtures/workspace_workspaceusers.json b/fixtures/workspace_workspaceusers.json new file mode 100644 index 0000000..cc4b92c --- /dev/null +++ b/fixtures/workspace_workspaceusers.json @@ -0,0 +1,22 @@ +[ + { + "id":3123855, + "uid":35224123, + "wid":777, + "admin":false, + "active":false, + "email":"John@toggl.com", + "at":"2013-05-17T16:50:36+03:00", + "name":"John Swift", + "invite_url":"https://toggl.com/user/accept_invitation?code=fb3ad3db5dasd123c2b529e3a519826" + },{ + "id":200066, + "uid":21961, + "wid":777, + "admin":true, + "active":true, + "email":"Jane@toggl.com", + "at":"2012-04-11T22:59:33+03:00", + "name":"Jane" + } +] diff --git a/fixtures/workspaces_get.json b/fixtures/workspaces_get.json new file mode 100644 index 0000000..75359ab --- /dev/null +++ b/fixtures/workspaces_get.json @@ -0,0 +1,28 @@ +[ + { + "id":3134975, + "name":"John's personal ws", + "premium":true, + "admin":true, + "default_hourly_rate":50, + "default_currency":"USD", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":true, + "rounding":1, + "rounding_minutes":15, + "at":"2013-08-28T16:22:21+00:00", + "logo_url":"my_logo.png" + },{ + "id":777, + "name":"My Company Inc", + "premium":true, + "admin":true, + "default_hourly_rate":40, + "default_currency":"EUR", + "only_admins_may_create_projects":false, + "only_admins_see_billable_rates":true, + "rounding":1, + "rounding_minutes":15, + "at":"2013-08-28T16:22:21+00:00" + } +] diff --git a/fixtures/workspaceuser_update.json b/fixtures/workspaceuser_update.json new file mode 100644 index 0000000..810e8d7 --- /dev/null +++ b/fixtures/workspaceuser_update.json @@ -0,0 +1,9 @@ +{ + "data": { + "id":19012628, + "uid":1625, + "wid":777, + "admin":false, + "active":true + } +} diff --git a/requirements.txt b/requirements.txt index ac2cf62..ab0edf6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,16 @@ +cffi==1.2.1 +cookies==2.2.1 +cryptography==1.0 +enum34==1.0.4 +funcsigs==0.4 +idna==2.0 +ipaddress==1.0.14 +mock==1.3.0 +ndg-httpsclient==0.4.0 +pbr==1.5.0 +pyasn1==0.1.8 +pycparser==2.14 +pyOpenSSL==0.15.1 requests==2.7.0 +responses==0.4.0 +six==1.9.0 diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..7ee4dca --- /dev/null +++ b/tests.py @@ -0,0 +1,175 @@ +import json +import os +import unittest + +import responses + +from togglwrapper import api +from togglwrapper.exceptions import AuthError + + +FAKE_TOKEN = 'fake_token_1' +FIXTURES_PATH = '%s/fixtures' % os.path.dirname(os.path.abspath(__file__)) + + +class TestTogglBase(unittest.TestCase): + """ Class to establish utility methods for Test classes. """ + + api_token = FAKE_TOKEN + focus_class = None + + def setUp(self): + self.toggl = api.Toggl(self.api_token) + + def get_json(self, filename): + """ Return the JSON data in the .json file with the given filename. """ + file_path = '{path}/{filename}.json'.format(path=FIXTURES_PATH, + filename=filename) + with open(file_path) as json_file: + json_dict = json.load(json_file) + json_file.close() + raw_json = json.dumps(json_dict) + return raw_json + + @property + def full_url(self): + return self.toggl.api_url + self.focus_class.uri + + +class TestToggl(TestTogglBase): + + @responses.activate + def test_wrong_token(self): + """ Should raise exception when wrong API token is provided. """ + full_url = self.toggl.api_url + self.toggl.User.uri + responses.add(responses.GET, full_url, status=403) + self.assertRaises(AuthError, self.toggl.User.get) + self.assertEqual(len(responses.calls), 1) + + +class TestClients(TestTogglBase): + focus_class = api.Clients + + @responses.activate + def test_create(self): + """ Should create a new Client. """ + responses.add( + responses.POST, + self.full_url, + body=self.get_json('client_create'), + status=200, + content_type='application/json' + ) + + new_client_data = {"client": {"name": "Very Big Company", "wid": 777}} + response = self.toggl.Clients.create(new_client_data) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_get_by_id(self): + """ Should get a specific Client by ID. """ + client_id = 1239455 + full_url = '{url}/{id}'.format(url=self.full_url, id=client_id) + responses.add( + responses.GET, + full_url, + body=self.get_json('client_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.Clients.get(id=client_id) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_get(self): + """ Should get all Clients. """ + responses.add( + responses.GET, + self.full_url, + body=self.get_json('clients_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.Clients.get() + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_update(self): + """ Should update a Client. """ + client_id = 1239455 + full_url = '{url}/{id}'.format(url=self.full_url, id=client_id) + responses.add( + responses.PUT, + full_url, + body=self.get_json('client_update'), + status=200, + content_type='application/json' + ) + + update_data = { + "client": { + "name": "Very Big Company", + "notes": "something about the client" + } + } + response = self.toggl.Clients.update(id=client_id, data=update_data) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_delete(self): + """ Should delete a Client. """ + client_id = 1239455 + full_url = '{url}/{id}'.format(url=self.full_url, id=client_id) + responses.add(responses.DELETE, full_url, status=200) + + response = self.toggl.Clients.delete(client_id) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + @responses.activate + def test_projects_get(self): + """ Should get all active projects under the Client. """ + client_id = 1239455 + url = '{url}/{id}/projects'.format(url=self.full_url, id=client_id) + responses.add( + responses.GET, + url, + body=self.get_json('client_projects_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.Clients.get_projects(client_id) + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + +class TestUser(TestTogglBase): + focus_class = api.User + + @responses.activate + def test_get(self): + """ Should successfully get the User associated with the token. """ + responses.add( + responses.GET, + self.full_url, + body=self.get_json('user_get'), + status=200, + content_type='application/json' + ) + + response = self.toggl.User.get() + + self.assertTrue(response) + self.assertEqual(len(responses.calls), 1) + + +if __name__ == '__main__' and __package__ is None: + __package__ = "toggl" + unittest.main() diff --git a/togglwrapper/__init__.py b/togglwrapper/__init__.py index e69de29..1b12be9 100644 --- a/togglwrapper/__init__.py +++ b/togglwrapper/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from .api import Toggl + +import logging +logging.getLogger(__name__) diff --git a/togglwrapper/api.py b/togglwrapper/api.py index 111029f..66dde0c 100644 --- a/togglwrapper/api.py +++ b/togglwrapper/api.py @@ -1,6 +1,10 @@ +import json + import requests from requests.auth import HTTPBasicAuth +from .decorators import error_checking, return_json + BASE_URL = 'https://www.toggl.com/api' API_VERSION = 'v8' @@ -8,37 +12,263 @@ class TogglObject(object): + @property def full_uri(self): return '{api}{uri}'.format(api=self.client.api_url, uri=self.uri) - def __init__(self, client): - self.client = client + def __init__(self, toggl): + self.toggl = toggl + + +class Get(object): + + def _compile_uri(self, id=None): + uri = self.uri + if id is not None: + uri += '/%s' % id + return uri + + def _get_child_objects(self, parent_id, child_uri, params=None): + """ + Get the Objects that belong to the parent Object with the given ID. + + Args: + parent_id (int): The ID of the parent Object. + child_uri (str): The uri of the child Object. e.g. If we wanted + the Clients of a Workspace, where the Workspace is the parent + object, the child-uri is '/clients'. + """ + uri = self._compile_uri(parent_id) + child_uri + return self.toggl.get(uri, params=params) + + def get(self, id=None, params=None): + """ Get the array of objects, or a specific instance by ID. """ + return self.toggl.get(self._compile_uri(id), params=params) + + +class Create(object): + def create(self, data): + """ Create a new instance of the object type. """ + return self.toggl.post(self.uri, data) + + +class Update(object): + def update(self, id=None, ids=None, data=None): + """ Update a specific instance by ID, or update multiple instances. """ + if not any((id, ids)) or (id and ids): + raise Exception('Must provide either an ID or an iterable of IDs.') + if id is not None: + uri = '{uri}/{id}'.format(uri=self.uri, id=id) + else: + uri = '{uri}/{ids}'.format(uri=self.uri, ids=','.join(ids)) + return self.toggl.put(uri, data) + + +class Delete(object): + def delete(self, id=None, ids=None): + """ Delete a specific instance by ID, or delete multiple instances. """ + if not any((id, ids)) or (id and ids): + raise Exception('Must provide either an ID or an iterable of IDs.') + if id is not None: + uri = '{uri}/{id}'.format(uri=self.uri, id=id) + else: + uri = '{uri}/{ids}'.format(uri=self.uri, ids=','.join(ids)) + return self.toggl.delete(uri) + + +class Clients(TogglObject, Get, Create, Update, Delete): + uri = '/clients' + + def get_projects(self, client_id, active=True): + """ + Get the projects associated with the Client with the given ID. + + Args: + client_id (int): The ID of the client. + active (bool or string, optional): Must be either True, False, or + the string 'both'. Defaults to True. + """ + cond1 = (active is True) + cond2 = (active is False) + cond3 = (active is 'both') + if not any((cond1, cond2, cond3)): + raise Exception("The 'active' param must be either True, False,", + "or 'both'.") + params = {'active': active} + return self._get_child_objects(client_id, '/projects', params=params) + + +class Dashboard(TogglObject, Get): + uri = 'dashboard' + + def get(self, workspace_id): + """ Get the Dashboard for the Workspace with the given ID. """ + return super(Dashboard, self).get(id=workspace_id) + + +class Projects(TogglObject, Get, Create, Update, Delete): + uri = '/projects' + + def get(self, project_id): + """ Get the Project with the given ID. """ + return super(Projects, self).get(id=project_id) + + def get_project_users(self, project_id): + """ Get the ProjectUsers for the Project with the given ID. """ + return self._get_child_objects(project_id, '/project_users') + + def get_tasks(self, project_id): + """ Get the Tasks for the Project with the given ID. """ + return self._get_child_objects(project_id, '/tasks') + + +class ProjectUsers(TogglObject, Create, Update, Delete): + uri = '/project_users' + def get_for_project(self, project_id): + """ Get the ProjectUsers for the Project with the given ID. """ + return self.toggl.Projects.get_project_users(project_id) -class User(TogglObject): + +class Tags(TogglObject, Create, Update, Delete): + uri = '/tags' + + +class Tasks(TogglObject, Get, Create, Update, Delete): + uri = '/tasks' + + def get(self, tag_id): + return super(Projects, self).get(id=tag_id) + + def get_for_project(self, project_id): + """ Get the Tasks for the Project with the given ID. """ + return self.toggl.Projects.get_tasks(project_id) + + +class TimeEntries(TogglObject, Get, Create, Update, Delete): + uri = '/time_entries' + + def get(self, id=None, start_date=None, end_date=None): + """ Get the time entry. """ + params = {'start_date': start_date, 'end_date': end_date} + return super(TimeEntries, self).get(id=id, params=params) + + def start(self, data): + """ Start a new time entry. """ + uri = self.uri + '/start' + return self.toggl.post(uri, data) + + def stop(self, time_entry_id): + """ Stop the time entry with the given ID. """ + uri = self.uri + '/{time_entry_id}' + return self.toggl.post(uri.format(time_entry_id=time_entry_id)) + + +class User(TogglObject, Get): uri = '/me' def get(self, related_data=False, since=None): """ Get the user associated with the current API token. """ - response = requests.get(self.full_uri, auth=self.client.auth) - return response.json() + params = {'related_data': related_data, 'since': since} + return super(User, self).get(params=params) def update(self, data): - """ Update the fields. """ - pass + """ Update the user associated with the api token. """ + return self.toggl.put(self.uri, data) + + +class Workspaces(TogglObject, Get, Update): + uri = '/workspaces' + + def get_users(self, workspace_id): + """ Get the Users for the Workspace with the given ID. """ + return self._get_child_objects(workspace_id, '/users') + def get_clients(self, workspace_id): + """ Get the Clients for the Workspace with the given ID. """ + return self._get_child_objects(workspace_id, '/clients') -class Client(object): + def get_projects(self, workspace_id): + """ Get the Projects for the Workspace with the given ID. """ + return self._get_child_objects(workspace_id, '/projects') + + def get_tasks(self, workspace_id): + """ Get the Tasks for the Workspace with the given ID. """ + return self._get_child_objects(workspace_id, '/tasks') + + def get_tags(self, workspace_id): + """ Get the Tags for the Workspace with the given ID. """ + return self._get_child_objects(workspace_id, '/tags') + + def get_workspace_users(self, workspace_id): + """ Get the Tags for the Workspace with the given ID. """ + return self._get_child_objects(workspace_id, '/workspace_users') + + def invite(self, workspace_id, data): + """ add users to workspace. """ + uri = '/workspaces/{workspace_id}/invite'.format(workspace_id) + self.toggl.post(uri, data) + + +class WorkspaceUsers(TogglObject, Update, Delete): + uri = '/workspace_users' + + +class Toggl(object): def __init__(self, api_token, base_url=BASE_URL, version=API_VERSION): - # self.api_token = api_token self.api_url = '{base}/{version}'.format(base=base_url, version=version) self.auth = HTTPBasicAuth(api_token, 'api_token') - self.user = User(self) + self.Clients = Clients(self) + self.User = User(self) + self.Projects = Projects(self) + self.ProjectUsers = ProjectUsers(self) + self.Tags = Tags(self) + self.Tasks = Tasks(self) + self.TimeEntries = TimeEntries(self) + self.User = User(self) + self.Workspaces = Workspaces(self) + self.WorkspaceUsers = WorkspaceUsers(self) + + def signups(self, user_info): + """ + Create a new user. + + Args: + user_info (dict): Values for all the required and optional fields. + """ + return self.post('/signups', {'user': user_info}) + + def reset_token(self): + """ Delete the current API Token and use a new token. """ + return self.post('/reset_token') + + @return_json + @error_checking + def get(self, uri, params=None): + """ GET to the given uri. """ + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) + return requests.get(full_uri, params=params, auth=self.auth) + + @return_json + @error_checking + def post(self, uri, data=None): + """ POST to the given uri with a data dict. """ + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) + payload = json.dumps(data) if data is not None else None + return requests.post(full_uri, data=payload, auth=self.auth) + + @return_json + @error_checking + def put(self, uri, data): + """ PUT to the given uri with a data dict. """ + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) + payload = json.dumps(data) + return requests.put(full_uri, data=payload, auth=self.auth) - def test_connection(self): - """ Test for a successful connection. """ - test_url = '{base}{uri}'.format(base=self.api_url, uri='test') - response = requests.get(test_url, auth=self.client.auth) - return response.json() + @error_checking + def delete(self, uri): + """ DELETE to the given uri. """ + full_uri = '{base}{uri}'.format(base=self.api_url, uri=uri) + return requests.delete(full_uri, auth=self.auth) diff --git a/togglwrapper/decorators.py b/togglwrapper/decorators.py new file mode 100644 index 0000000..cd200bd --- /dev/null +++ b/togglwrapper/decorators.py @@ -0,0 +1,22 @@ +from .exceptions import AuthError + + +def return_json(func): + """ Returns the JSON content of a requests.Response. """ + def inner(*args, **kwargs): + response = func(*args, **kwargs) + return response.json() + return inner + + +def error_checking(func): + """ Raises exceptions if the response did not return 200 OK. """ + def inner(*args, **kwargs): + response = func(*args, **kwargs) + # Status code of 403 Forbidden means incorrect API token/wrong auth + if response.status_code == 403: + raise AuthError('Incorrect API token.') + # Raise an HTTPError if status code isn't 200 + response.raise_for_status() + return response + return inner diff --git a/togglwrapper/exceptions.py b/togglwrapper/exceptions.py new file mode 100644 index 0000000..b40d353 --- /dev/null +++ b/togglwrapper/exceptions.py @@ -0,0 +1,2 @@ +class AuthError(Exception): + """ Raised when authentication fails. """ diff --git a/togglwrapper/tests.py b/togglwrapper/tests.py deleted file mode 100644 index 8f1e42c..0000000 --- a/togglwrapper/tests.py +++ /dev/null @@ -1,19 +0,0 @@ -import unittest - -import api - - -class TestAPIMethods(unittest.TestCase): - def test_client(self): - """ Should successfully establish the client. """ - api_token = 'correct_token' - self.assertTrue(api.Client(api_token)) - - def test_client_wrong_token(self): - """ Should raise exception when wrong API token is provided. """ - wrong_api_token = 'wrong token' - self.assertRaises(Exception, api.Client, wrong_api_token) - - -if __name__ == '__main__': - unittest.main()