diff options
Diffstat (limited to 'timetask')
| -rw-r--r-- | timetask/fields.go | 85 | ||||
| -rw-r--r-- | timetask/generator.go | 44 | ||||
| -rw-r--r-- | timetask/http.go | 239 | ||||
| -rw-r--r-- | timetask/http_test.go | 4 | ||||
| -rw-r--r-- | timetask/module.go | 24 | ||||
| -rw-r--r-- | timetask/module_test.go | 50 | ||||
| -rw-r--r-- | timetask/profile.go | 5 | ||||
| -rw-r--r-- | timetask/project.go | 10 | ||||
| -rw-r--r-- | timetask/time_entry.go | 65 | 
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&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&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  }  | 
