aboutsummaryrefslogtreecommitdiffstats
path: root/timetask
diff options
context:
space:
mode:
authorTeddy Wing2017-06-04 02:07:50 +0200
committerTeddy Wing2017-06-04 02:07:50 +0200
commit02e4fb5d0d95b8c5c5442ee0a97b960f1296c236 (patch)
tree4cf323694692322036b80d2d921ff966096c6c36 /timetask
parent055301ca09d57b759b290d897bbb7560460251ca (diff)
parent9b6a6543e351308939bd420243507368b0669e63 (diff)
downloadtimetasker-02e4fb5d0d95b8c5c5442ee0a97b960f1296c236.tar.bz2
Merge branch 'timetasker-daily'
Diffstat (limited to 'timetask')
-rw-r--r--timetask/fields.go85
-rw-r--r--timetask/generator.go44
-rw-r--r--timetask/http.go239
-rw-r--r--timetask/http_test.go4
-rw-r--r--timetask/module.go24
-rw-r--r--timetask/module_test.go50
-rw-r--r--timetask/profile.go5
-rw-r--r--timetask/project.go10
-rw-r--r--timetask/time_entry.go65
9 files changed, 265 insertions, 261 deletions
diff --git a/timetask/fields.go b/timetask/fields.go
deleted file mode 100644
index fb3a026..0000000
--- a/timetask/fields.go
+++ /dev/null
@@ -1,85 +0,0 @@
-package timetask
-
-import "fmt"
-
-type Client struct {
- ID int
- Name string
- Projects []Project
-}
-
-type Project struct {
- ID int
- Name string
- Modules []Module
- Tasks []Task
- WorkTypes []WorkType `yaml:"work_types"`
-}
-
-type Module struct {
- ID int
- Name string
-}
-type Task struct {
- ID int
- Name string
-}
-type WorkType struct {
- ID int
- Name string
-}
-
-type Fields struct {
- PersonID int `yaml:"person_id"`
- Clients []Client
-}
-
-func (f *Fields) ClientByName(client_name string) (*Client, error) {
- for _, client := range f.Clients {
- if client.Name == client_name {
- return &client, nil
- }
- }
-
- return nil, fmt.Errorf("Client %s not found", client_name)
-}
-
-func (c *Client) ProjectByName(project_name string) (*Project, error) {
- for _, project := range c.Projects {
- if project.Name == project_name {
- return &project, nil
- }
- }
-
- return nil, fmt.Errorf("Project %s not found", project_name)
-}
-
-func (p *Project) ModuleByName(module_name string) (*Module, error) {
- for _, module := range p.Modules {
- if module.Name == module_name {
- return &module, nil
- }
- }
-
- return nil, fmt.Errorf("Module %s not found", module_name)
-}
-
-func (p *Project) TaskByName(task_name string) (*Task, error) {
- for _, task := range p.Tasks {
- if task.Name == task_name {
- return &task, nil
- }
- }
-
- return nil, fmt.Errorf("Task %s not found", task_name)
-}
-
-func (p *Project) WorkTypeByName(work_type_name string) (*WorkType, error) {
- for _, work_type := range p.WorkTypes {
- if work_type.Name == work_type_name {
- return &work_type, nil
- }
- }
-
- return nil, fmt.Errorf("Work type %s not found", work_type_name)
-}
diff --git a/timetask/generator.go b/timetask/generator.go
deleted file mode 100644
index 5d0fa7f..0000000
--- a/timetask/generator.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package timetask
-
-import (
- "io"
- "log"
- "text/template"
- "time"
-
- "github.com/olebedev/when"
- "github.com/olebedev/when/rules/common"
- "github.com/olebedev/when/rules/en"
-)
-
-func GenerateWeeklyTimesheet(wr io.Writer, defaults TimeEntry) {
- w := when.New(nil)
- w.Add(en.All...)
- w.Add(common.All...)
-
- monday, err := w.Parse("last monday", time.Now())
- if err != nil {
- log.Panic(err)
- }
-
- time_entries := []TimeEntry{}
- day := monday.Time
- for i := 1; i <= 5; i++ {
- time_entries = append(time_entries, defaults)
- time_entries[len(time_entries) - 1].Date = day
- day = day.AddDate(0, 0, 1) // Add 1 day
- }
-
- t, err := template.ParseFiles(
- "templates/weekly_timesheet.yml.tmpl",
- "templates/timesheet.yml.tmpl",
- )
- if err != nil {
- log.Panic(err)
- }
-
- err = t.Execute(wr, time_entries)
- if err != nil {
- log.Panic(err)
- }
-}
diff --git a/timetask/http.go b/timetask/http.go
index 83946ad..f0a6548 100644
--- a/timetask/http.go
+++ b/timetask/http.go
@@ -1,8 +1,9 @@
package timetask
import (
+ "bytes"
"fmt"
- "log"
+ "io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
@@ -12,15 +13,17 @@ import (
"golang.org/x/net/publicsuffix"
)
-func Login(username, password string) (resp *http.Response, err error) {
+var baseURL string = "https://af83.timetask.com/index.php"
+
+func Login(username, password string) (client *http.Client, err error) {
cookies, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return nil, err
}
- client := http.Client{Jar: cookies}
- resp, err = client.PostForm(
- "https://af83.timetask.com/index.php",
+ client = &http.Client{Jar: cookies}
+ resp, err := client.PostForm(
+ baseURL,
url.Values{
"module": {"people"},
"action": {"loginsubmit"},
@@ -30,112 +33,166 @@ func Login(username, password string) (resp *http.Response, err error) {
},
)
if err != nil {
- return resp, err
+ return client, err
+ }
+
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return client, err
}
- return resp, err
+ if strings.Contains(
+ string(body),
+ "The username and password don't appear to be valid.",
+ ) {
+ return client, fmt.Errorf("TimeTask authentication failed")
+ }
+
+ return client, err
}
-func SubmitTimeEntries(fields Fields, time_entries []TimeEntry) (resp *http.Response, err error) {
- v := buildSubmissionParams(fields, time_entries)
+func SubmitTimeEntry(client http.Client, time_entry TimeEntry) error {
+ values := buildSubmissionParams(time_entry)
+
+ values.Set("module", "time")
+ values.Set("action", "submitmultipletime")
+
+ resp, err := client.PostForm(
+ baseURL,
+ values,
+ )
+ if err != nil {
+ return err
+ }
+
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return err
+ }
- v.Set("module", "time")
- v.Set("action", "submitmultipletime")
+ if strings.Contains(
+ string(body),
+ "No time entries were created.",
+ ) {
+ return fmt.Errorf("time entry creation failed")
+ }
- return nil, nil
+ return nil
}
-func buildSubmissionParams(fields Fields, time_entries []TimeEntry) url.Values {
+func buildSubmissionParams(time_entry TimeEntry) url.Values {
v := url.Values{}
- entry_indexes := []string{}
-
- for i, entry := range time_entries {
- entry_indexes = append(entry_indexes, strconv.Itoa(i))
-
- client, err := fields.ClientByName(entry.Client)
- if err != nil {
- log.Panic(err)
- }
-
- project, err := client.ProjectByName(entry.Project)
- if err != nil {
- log.Panic(err)
- }
-
- module, err := project.ModuleByName(entry.Module)
- if err != nil {
- log.Panic(err)
- }
-
- task, err := project.TaskByName(entry.Task)
- if err != nil {
- log.Panic(err)
- }
-
- work_type, err := project.WorkTypeByName(entry.WorkType)
- if err != nil {
- log.Panic(err)
- }
-
- var billable string
- if entry.Billable {
- billable = "t"
- } else {
- billable = "f"
- }
-
- v.Set(
- fmt.Sprintf("f_personID%d", i),
- strconv.Itoa(fields.PersonID),
- )
- v.Set(
- fmt.Sprintf("f_clientID%d", i),
- strconv.Itoa(client.ID),
- )
+ v.Set(
+ "f_personID0",
+ strconv.Itoa(time_entry.PersonID),
+ )
- v.Set(
- fmt.Sprintf("f_projectID%d", i),
- strconv.Itoa(project.ID),
- )
+ v.Set(
+ "f_clientID0",
+ strconv.Itoa(time_entry.Client),
+ )
- v.Set(
- fmt.Sprintf("f_moduleID%d", i),
- strconv.Itoa(module.ID),
- )
+ v.Set(
+ "f_projectID0",
+ strconv.Itoa(time_entry.Project),
+ )
- v.Set(
- fmt.Sprintf("f_taskID%d", i),
- strconv.Itoa(task.ID),
- )
+ v.Set(
+ "f_moduleID0",
+ strconv.Itoa(time_entry.Module),
+ )
- v.Set(
- fmt.Sprintf("f_worktypeID%d", i),
- strconv.Itoa(work_type.ID),
- )
+ v.Set(
+ "f_taskID0",
+ strconv.Itoa(time_entry.Task),
+ )
- v.Set(
- fmt.Sprintf("f_date%d", i),
- entry.Date.Format("02/01/06"), // day/month/year
- )
+ v.Set(
+ "f_worktypeID0",
+ strconv.Itoa(time_entry.WorkType),
+ )
- v.Set(
- fmt.Sprintf("f_time%d", i),
- strconv.Itoa(entry.Time),
- )
+ v.Set(
+ "f_date0",
+ time_entry.Date.Format("02/01/06"), // day/month/year
+ )
- v.Set(
- fmt.Sprintf("f_billable%d", i),
- billable,
- )
+ time_str := strconv.FormatFloat(time_entry.Time, 'f', 2, 64)
+ time_european_format := strings.Replace(time_str, ".", ",", -1)
+ v.Set(
+ "f_time0",
+ time_european_format,
+ )
- v.Set(
- fmt.Sprintf("f_description%d", i),
- entry.Description,
- )
+ var billable string
+ if time_entry.Billable {
+ billable = "t"
+ } else {
+ billable = "f"
}
- v.Set("f_entryIndexes", strings.Join(entry_indexes, ","))
+ v.Set(
+ "f_billable0",
+ billable,
+ )
+
+ v.Set(
+ "f_description0",
+ time_entry.Description,
+ )
+
+ v.Set("f_entryIndexes", "0")
return v
}
+
+func RequestModules(
+ client http.Client,
+ time_entry TimeEntry,
+) (string, error) {
+ params := url.Values{
+ "module": {"projects"},
+ "action": {"listmodulesxref"},
+ "f_ID": {strconv.Itoa(time_entry.Project)},
+ "f_active": {"t"},
+ "f_clientID": {strconv.Itoa(time_entry.Client)},
+ "f_personID": {strconv.Itoa(time_entry.PersonID)},
+ "f_milestoneID": {""},
+ }
+ modules_url, err := url.Parse(baseURL)
+ if err != nil {
+ return "", err
+ }
+
+ modules_url.RawQuery = params.Encode()
+
+ resp, err := client.Get(modules_url.String())
+ if err != nil {
+ return "", err
+ }
+
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", err
+ }
+ response_body := string(body)
+
+ modules, err := ModuleParseXML(response_body)
+ if err != nil {
+ return "", err
+ }
+
+ var module_buf bytes.Buffer
+ module_buf.WriteString("ID\tModule\n")
+ for _, module := range modules {
+ module_buf.WriteString(
+ fmt.Sprintf("%d\t%s\n", module.ID, module.Name),
+ )
+ }
+
+ return module_buf.String(), nil
+}
diff --git a/timetask/http_test.go b/timetask/http_test.go
index c54617f..36f9e4a 100644
--- a/timetask/http_test.go
+++ b/timetask/http_test.go
@@ -17,7 +17,9 @@ func init() {
}
func TestLogin(t *testing.T) {
- response, err := Login(username, password)
+ t.Skip("No requests")
+
+ response, _, err := Login(username, password)
if err != nil {
t.Fatal(err)
}
diff --git a/timetask/module.go b/timetask/module.go
new file mode 100644
index 0000000..4dde57a
--- /dev/null
+++ b/timetask/module.go
@@ -0,0 +1,24 @@
+package timetask
+
+import (
+ "encoding/xml"
+)
+
+type Module struct {
+ ID int `xml:"moduleid"`
+ Name string `xml:"modulename"`
+}
+
+type moduleXML struct {
+ Modules []Module `xml:"response>item"`
+}
+
+func ModuleParseXML(xml_str string) ([]Module, error) {
+ modules := moduleXML{}
+ err := xml.Unmarshal([]byte(xml_str), &modules)
+ if err != nil {
+ return nil, err
+ }
+
+ return modules.Modules, nil
+}
diff --git a/timetask/module_test.go b/timetask/module_test.go
new file mode 100644
index 0000000..cee87c5
--- /dev/null
+++ b/timetask/module_test.go
@@ -0,0 +1,50 @@
+package timetask
+
+import "testing"
+
+const modules_xml = `<?xml version="1.0" encoding="UTF-8" ?>
+<ajax-response>
+ <response type="object" id="ModuleList">
+ <item>
+ <moduleid><![CDATA[55555]]></moduleid>
+ <modulename><![CDATA[R&amp;D]]></modulename>
+ </item>
+ <item>
+ <moduleid><![CDATA[77777]]></moduleid>
+ <modulename><![CDATA[Sprint 1]]></modulename>
+ </item>
+ <item>
+ <moduleid><![CDATA[222222]]></moduleid>
+ <modulename><![CDATA[Sprint 2]]></modulename>
+ </item>
+ </response>
+</ajax-response>`
+
+func TestModuleParseXML(t *testing.T) {
+ modules, err := ModuleParseXML(modules_xml)
+ if err != nil {
+ t.Error(err)
+ }
+
+ _ = []Module{ // wanted
+ Module{
+ ID: 55555,
+ Name: "R&amp;D",
+ },
+ Module{
+ ID: 77777,
+ Name: "Sprint 1",
+ },
+ Module{
+ ID: 222222,
+ Name: "Sprint 2",
+ },
+ }
+
+ // Need a way to compare slices
+ // if modules != wanted {
+ // t.Errorf("Module parsing failed. Wanted %+v got %+v", wanted, modules)
+ // }
+
+ t.Logf("%+v\n", modules)
+}
diff --git a/timetask/profile.go b/timetask/profile.go
new file mode 100644
index 0000000..9267ee6
--- /dev/null
+++ b/timetask/profile.go
@@ -0,0 +1,5 @@
+package timetask
+
+type Profile struct {
+ PersonID int `toml:"person_id"`
+}
diff --git a/timetask/project.go b/timetask/project.go
new file mode 100644
index 0000000..b8fe5c7
--- /dev/null
+++ b/timetask/project.go
@@ -0,0 +1,10 @@
+package timetask
+
+type Project struct {
+ Client int
+ Project int
+ Module int
+ Task int
+ WorkType int `toml:"work_type"`
+ Billable bool
+}
diff --git a/timetask/time_entry.go b/timetask/time_entry.go
index 17a8e0c..bb7a741 100644
--- a/timetask/time_entry.go
+++ b/timetask/time_entry.go
@@ -3,50 +3,35 @@ package timetask
import "time"
type TimeEntry struct {
- Client string
- Project string
- Module string
- Task string
- WorkType string `yaml:"work_type"`
+ PersonID int
+ Client int
+ Project int
+ Module int
+ Task int
+ WorkType int
Date time.Time
- Time int
+ Time float64
Billable bool
Description string
}
-// Parse date string into a real date
-func (te *TimeEntry) UnmarshalYAML(unmarshal func(interface{}) error) error {
- var auxiliary struct {
- Client string
- Project string
- Module string
- Task string
- WorkType string `yaml:"work_type"`
- Date string
- Time int
- Billable bool
- Description string
+func NewTimeEntry(
+ profile Profile,
+ project Project,
+ date time.Time,
+ time float64,
+ description string,
+) TimeEntry {
+ return TimeEntry{
+ PersonID: profile.PersonID,
+ Client: project.Client,
+ Project: project.Project,
+ Module: project.Module,
+ Task: project.Task,
+ WorkType: project.WorkType,
+ Date: date,
+ Time: time,
+ Billable: project.Billable,
+ Description: description,
}
-
- err := unmarshal(&auxiliary)
- if err != nil {
- return err
- }
-
- date, err := time.Parse("2006-01-02", auxiliary.Date)
- if auxiliary.Date != "" && err != nil {
- return err
- }
-
- te.Client = auxiliary.Client
- te.Project = auxiliary.Project
- te.Module = auxiliary.Module
- te.Task = auxiliary.Task
- te.WorkType = auxiliary.WorkType
- te.Date = date
- te.Time = auxiliary.Time
- te.Billable = auxiliary.Billable
- te.Description = auxiliary.Description
-
- return nil
}