-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add task assignments and day entries
- Loading branch information
Showing
18 changed files
with
1,421 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,4 +9,12 @@ func TestNewBasicAuthAPI(t *testing.T) { | |
if a1.BaseURL != "https://example.harvestapp.com" { | ||
t.Errorf("Incorrect domain name '%s'.", a1.BaseURL) | ||
} | ||
if a1.client == nil { | ||
t.Error("No http client") | ||
} | ||
} | ||
|
||
func testAPI() *API { | ||
a := NewBasicAuthAPI("example", "[email protected]", "password") | ||
return a | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
|
||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"> | ||
<style> | ||
body { | ||
background: black; | ||
color: rgb(80, 80, 80); | ||
} | ||
body, pre, #legend span { | ||
font-family: Menlo, monospace; | ||
font-weight: bold; | ||
} | ||
#topbar { | ||
background: black; | ||
position: fixed; | ||
top: 0; left: 0; right: 0; | ||
height: 42px; | ||
border-bottom: 1px solid rgb(80, 80, 80); | ||
} | ||
#content { | ||
margin-top: 50px; | ||
} | ||
#nav, #legend { | ||
float: left; | ||
margin-left: 10px; | ||
} | ||
#legend { | ||
margin-top: 12px; | ||
} | ||
#nav { | ||
margin-top: 10px; | ||
} | ||
#legend span { | ||
margin: 0 5px; | ||
} | ||
.cov0 { color: rgb(192, 0, 0) } | ||
.cov1 { color: rgb(128, 128, 128) } | ||
.cov2 { color: rgb(116, 140, 131) } | ||
.cov3 { color: rgb(104, 152, 134) } | ||
.cov4 { color: rgb(92, 164, 137) } | ||
.cov5 { color: rgb(80, 176, 140) } | ||
.cov6 { color: rgb(68, 188, 143) } | ||
.cov7 { color: rgb(56, 200, 146) } | ||
.cov8 { color: rgb(44, 212, 149) } | ||
.cov9 { color: rgb(32, 224, 152) } | ||
.cov10 { color: rgb(20, 236, 155) } | ||
|
||
</style> | ||
</head> | ||
<body> | ||
<div id="topbar"> | ||
<div id="nav"> | ||
<select id="files"> | ||
|
||
<option value="file0">github.com/adlio/harvest/api.go (76.0%)</option> | ||
|
||
<option value="file1">github.com/adlio/harvest/arguments.go (80.0%)</option> | ||
|
||
<option value="file2">github.com/adlio/harvest/project.go (57.1%)</option> | ||
|
||
</select> | ||
</div> | ||
<div id="legend"> | ||
<span>not tracked</span> | ||
|
||
<span class="cov0">not covered</span> | ||
<span class="cov8">covered</span> | ||
|
||
</div> | ||
</div> | ||
<div id="content"> | ||
|
||
<pre class="file" id="file0" style="display: none">package harvest | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
|
||
"github.com/pkg/errors" | ||
) | ||
|
||
const HARVEST_DOMAIN = "harvestapp.com" | ||
|
||
type API struct { | ||
client *http.Client | ||
BaseURL string | ||
SubDomain string | ||
User string | ||
Password string | ||
} | ||
|
||
func NewBasicAuthAPI(subdomain, user, password string) *API <span class="cov8" title="1">{ | ||
a := API{} | ||
a.client = http.DefaultClient | ||
a.SubDomain = subdomain | ||
a.User = user | ||
a.Password = password | ||
a.BaseURL = fmt.Sprintf("https://%s.%s", subdomain, HARVEST_DOMAIN) | ||
return &a | ||
}</span> | ||
|
||
func (a *API) Get(path string, args Arguments, target interface{}) error <span class="cov8" title="1">{ | ||
url := fmt.Sprintf("%s/%s", a.BaseURL, path) | ||
urlWithParams := fmt.Sprintf("%s?%s", url, args.ToURLValues().Encode()) | ||
|
||
req, err := http.NewRequest("GET", urlWithParams, nil) | ||
if err != nil </span><span class="cov0" title="0">{ | ||
return errors.Wrapf(err, "Invalid GET request %s", url) | ||
}</span> | ||
|
||
<span class="cov8" title="1">resp, err := a.client.Do(req) | ||
if err != nil </span><span class="cov0" title="0">{ | ||
return errors.Wrapf(err, "HTTP request failure on %s", url) | ||
}</span> | ||
<span class="cov8" title="1">defer resp.Body.Close() | ||
if resp.StatusCode != 200 </span><span class="cov0" title="0">{ | ||
body, err := ioutil.ReadAll(resp.Body) | ||
return errors.Errorf("HTTP request failure on %s: %s %s", url, string(body), err) | ||
}</span> | ||
|
||
<span class="cov8" title="1">decoder := json.NewDecoder(resp.Body) | ||
err = decoder.Decode(target) | ||
if err != nil </span><span class="cov0" title="0">{ | ||
body, _ := ioutil.ReadAll(resp.Body) | ||
return errors.Wrapf(err, "JSON decode failed on %s: %s", url, string(body)) | ||
}</span> | ||
|
||
<span class="cov8" title="1">return nil</span> | ||
} | ||
</pre> | ||
|
||
<pre class="file" id="file1" style="display: none">// Copyright © 2016 Aaron Longwell | ||
// | ||
// Use of this source code is governed by an MIT licese. | ||
// Details in the LICENSE file. | ||
|
||
package harvest | ||
|
||
import ( | ||
"net/url" | ||
) | ||
|
||
type Arguments map[string]string | ||
|
||
func Defaults() Arguments <span class="cov8" title="1">{ | ||
return make(Arguments) | ||
}</span> | ||
|
||
func (args Arguments) ToURLValues() url.Values <span class="cov8" title="1">{ | ||
v := url.Values{} | ||
for key, value := range args </span><span class="cov0" title="0">{ | ||
v.Set(key, value) | ||
}</span> | ||
<span class="cov8" title="1">return v</span> | ||
} | ||
</pre> | ||
|
||
<pre class="file" id="file2" style="display: none">package harvest | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
type ProjectResponse struct { | ||
Project *Project `json:"project"` | ||
} | ||
|
||
type Project struct { | ||
ID int64 `json:"id"` | ||
ClientID int64 `json:"client_id"` | ||
Name string `json:"name"` | ||
Code string | ||
Active bool | ||
Billable bool | ||
BillBy string | ||
HourlyRate *float64 | ||
BudgetBy string | ||
Budget *float64 | ||
NotifyWhenOverBudget bool | ||
OverBudgetNotificationPercentage float64 | ||
OverBudgetNotifiedAt time.Time | ||
ShowBudgetToAll bool | ||
CreatedAt time.Time | ||
UpdatedAt time.Time | ||
StartsOn *Date | ||
EndsOn *Date | ||
Estimate *float64 | ||
EstimateBy string | ||
Notes string | ||
CostBudget *float64 | ||
CostBudgetIncludeExpenses bool | ||
} | ||
|
||
func (a *API) GetProject(projectID int64, args Arguments) (project *Project, err error) <span class="cov8" title="1">{ | ||
projectResponse := ProjectResponse{} | ||
path := fmt.Sprintf("/projects/%v", projectID) | ||
err = a.Get(path, args, &projectResponse) | ||
return projectResponse.Project, err | ||
}</span> | ||
|
||
func (a *API) GetProjects(args Arguments) (projects []*Project, err error) <span class="cov0" title="0">{ | ||
path := fmt.Sprintf("/projects") | ||
err = a.Get(path, args, &projects) | ||
return | ||
}</span> | ||
</pre> | ||
|
||
</div> | ||
</body> | ||
<script> | ||
(function() { | ||
var files = document.getElementById('files'); | ||
var visible; | ||
files.addEventListener('change', onChange, false); | ||
function select(part) { | ||
if (visible) | ||
visible.style.display = 'none'; | ||
visible = document.getElementById(part); | ||
if (!visible) | ||
return; | ||
files.value = part; | ||
visible.style.display = 'block'; | ||
location.hash = part; | ||
} | ||
function onChange() { | ||
select(files.value); | ||
window.scrollTo(0, 0); | ||
} | ||
if (location.hash != "") { | ||
select(location.hash.substr(1)); | ||
} | ||
if (!visible) { | ||
select("file0"); | ||
} | ||
})(); | ||
</script> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
mode: set | ||
github.com/adlio/harvest/arguments.go:14.27,16.2 1 1 | ||
github.com/adlio/harvest/arguments.go:18.48,20.31 2 1 | ||
github.com/adlio/harvest/arguments.go:23.2,23.10 1 1 | ||
github.com/adlio/harvest/arguments.go:20.31,22.3 1 0 | ||
github.com/adlio/harvest/project.go:38.89,43.2 4 1 | ||
github.com/adlio/harvest/project.go:45.76,49.2 3 0 | ||
github.com/adlio/harvest/api.go:22.61,30.2 7 1 | ||
github.com/adlio/harvest/api.go:32.74,37.16 4 1 | ||
github.com/adlio/harvest/api.go:41.2,42.16 2 1 | ||
github.com/adlio/harvest/api.go:45.2,46.28 2 1 | ||
github.com/adlio/harvest/api.go:51.2,53.16 3 1 | ||
github.com/adlio/harvest/api.go:58.2,58.12 1 1 | ||
github.com/adlio/harvest/api.go:37.16,39.3 1 0 | ||
github.com/adlio/harvest/api.go:42.16,44.3 1 0 | ||
github.com/adlio/harvest/api.go:46.28,49.3 2 0 | ||
github.com/adlio/harvest/api.go:53.16,56.3 2 0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
#!/bin/bash | ||
|
||
go test -coverprofile=coverage.out | ||
go tool cover -html=coverage.out -o coverage.html | ||
open coverage.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package harvest | ||
|
||
import ( | ||
"fmt" | ||
"time" | ||
) | ||
|
||
type DayEntryResponse struct { | ||
DayEntries []*DayEntry `json:"day_entries"` | ||
} | ||
|
||
type DayEntry struct { | ||
ID int64 `json:"id"` | ||
UserID int64 `json:"user_id"` | ||
SpentAt string `json:"spent_at"` | ||
CreatedAt time.Time `json:"created_at"` | ||
UpdatedAt time.Time `json:"updated_at"` | ||
ProjectID string `json:"project_id"` | ||
TaskID string `json:"task_id"` | ||
Project string `json:"project"` | ||
Task string `json:"task"` | ||
Client string `json:"client"` | ||
Notes string `json:"notes"` | ||
HoursWithoutTimer int64 `json:"hours_without_timer"` | ||
Hours int64 `json:"hours"` | ||
} | ||
|
||
func (a *API) GetTodayEntry(args Arguments) (dayentries []*DayEntry, err error) { | ||
dayEntriesResponse := DayEntryResponse{} | ||
path := fmt.Sprintf("/daily?slim=1") | ||
err = a.Get(path, args, &dayEntriesResponse) | ||
return dayEntriesResponse.DayEntries, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package harvest | ||
|
||
import "testing" | ||
|
||
func TestGetTodayEntry(t *testing.T) { | ||
a := testAPI() | ||
dayEntryResponse := mockResponse("dayentries", "today-example.json") | ||
a.BaseURL = dayEntryResponse.URL | ||
dayentries, err := a.GetTodayEntry(Defaults()) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if len(dayentries) != 2 { | ||
t.Errorf("Incorrect number of entries '%v'", len(dayentries)) | ||
} | ||
if dayentries[0].ID != 538242480 { | ||
t.Errorf("Incorrect day entry ID '%v'", dayentries[0].ID) | ||
} | ||
if dayentries[1].UserID != 1420761 { | ||
t.Errorf("Incorrect UserID '%v'", dayentries[1].ID) | ||
} | ||
} |
Oops, something went wrong.