From 8b608aa4dfdff5e1e17664cff8f2f602d326844c Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 3 Jun 2017 22:30:34 +0200 Subject: Update TODO --- TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO b/TODO index 2065140..1c5c696 100644 --- a/TODO +++ b/TODO @@ -19,3 +19,5 @@ v Make `PasswordCmd` work - Request and list module IDs and names for a given project alias v Format float error ("700") (2017.06.03) + +- Move HTTP errors into http.go -- cgit v1.2.3 From d833ffe6cd5efd97b00f907ed426a88275a8c8db Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 3 Jun 2017 23:08:08 +0200 Subject: Add an option to list "Modules" (sprints & their IDs) Sprints will change with time while the other IDs of a project will stay the same. Thus sprints, which live in the `Module` field, must be updated regularly. In order to facilitate that updating, instead of requiring users to get those IDs directly from the TimeTask website every time, have them use this command to get the names of sprints and their IDs. They can then update the ID manually in their config file. This code makes a request to the endpoint that returns module IDs for the site (the site queries this via AJAX to update its interface). The result of the request is some XML containing the modules and their IDs. For now I'm just printing this out. We'll want to parse the XML and display it in a nicer way. --- main.go | 10 ++++++++++ timetask/http.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/main.go b/main.go index 861dc8b..1521821 100644 --- a/main.go +++ b/main.go @@ -42,6 +42,7 @@ func main() { ) write_config := kingpin.Flag("write-config", write_config_description). Bool() + list_modules := kingpin.Flag("list-modules", "List sprints with IDs").Bool() kingpin.Version(VERSION) kingpin.Parse() @@ -108,6 +109,15 @@ func main() { os.Exit(1) } + // List modules + if *list_modules { + modules, err := timetask.RequestModules(*client, time_entry) + kingpin.FatalIfError(err, "could not retrieve sprints") + fmt.Println(modules) + + os.Exit(0) + } + resp, err = timetask.SubmitTimeEntry(*client, time_entry) kingpin.FatalIfError(err, "time entry submission request failed") diff --git a/timetask/http.go b/timetask/http.go index 8c41b4f..3ff6f4f 100644 --- a/timetask/http.go +++ b/timetask/http.go @@ -1,6 +1,7 @@ package timetask import ( + "io/ioutil" "net/http" "net/http/cookiejar" "net/url" @@ -126,3 +127,38 @@ func buildSubmissionParams(time_entry TimeEntry) url.Values { 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) + + return response_body, nil +} -- cgit v1.2.3 From aff9a869ffab0eeb8c644826b9250a500468ff1a Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sat, 3 Jun 2017 23:37:32 +0200 Subject: TestLogin(): Fix `Login()` assignment mismatch `Login()` now returns 3 values. Update the test. --- timetask/http_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/timetask/http_test.go b/timetask/http_test.go index 604c3c3..36f9e4a 100644 --- a/timetask/http_test.go +++ b/timetask/http_test.go @@ -19,7 +19,7 @@ func init() { func TestLogin(t *testing.T) { t.Skip("No requests") - response, err := Login(username, password) + response, _, err := Login(username, password) if err != nil { t.Fatal(err) } -- cgit v1.2.3 From c113de965dea24c44023a9aabcff7f8ee1f0caa0 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 4 Jun 2017 00:05:39 +0200 Subject: Add module.go for parsing Module XML A new function that parses the XML returned by the `RequestModules()` function. It provides a `Module` type that allows us to interact with modules more easily in code. The `ParseXML()` function will take an XML string and return a slice of `Module`s. Added a test just to facilitate development. Wasn't able to find an easy way to compare slices in Go, so just printed the values and checked the result visually. Not a useful test for future use, but it served its purpose. Eventually it would be nice to find a way to compare structs and have a real pass/fail condition. --- timetask/module.go | 24 ++++++++++++++++++++++++ timetask/module_test.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 timetask/module.go create mode 100644 timetask/module_test.go diff --git a/timetask/module.go b/timetask/module.go new file mode 100644 index 0000000..adef4e5 --- /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 ParseXML(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..6dcfa94 --- /dev/null +++ b/timetask/module_test.go @@ -0,0 +1,50 @@ +package timetask + +import "testing" + +const modules_xml = ` + + + + + + + + + + + + + + + +` + +func TestParseXML(t *testing.T) { + modules, err := ParseXML(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) +} -- cgit v1.2.3 From 7e711772def7a5d711500c3c6c37ea41b57ac598 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 4 Jun 2017 00:10:14 +0200 Subject: Change `ParseXML()` name to `ModuleParseXML()` Prefix the function name to make it more obvious what it relates to. Since this function lives in the `timetask` module and will be used in contexts that have nothing to do with Modules. --- timetask/module.go | 2 +- timetask/module_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/timetask/module.go b/timetask/module.go index adef4e5..4dde57a 100644 --- a/timetask/module.go +++ b/timetask/module.go @@ -13,7 +13,7 @@ type moduleXML struct { Modules []Module `xml:"response>item"` } -func ParseXML(xml_str string) ([]Module, error) { +func ModuleParseXML(xml_str string) ([]Module, error) { modules := moduleXML{} err := xml.Unmarshal([]byte(xml_str), &modules) if err != nil { diff --git a/timetask/module_test.go b/timetask/module_test.go index 6dcfa94..cee87c5 100644 --- a/timetask/module_test.go +++ b/timetask/module_test.go @@ -20,8 +20,8 @@ const modules_xml = ` ` -func TestParseXML(t *testing.T) { - modules, err := ParseXML(modules_xml) +func TestModuleParseXML(t *testing.T) { + modules, err := ModuleParseXML(modules_xml) if err != nil { t.Error(err) } -- cgit v1.2.3 From eb82e5e447dff67d5bcd1a2d5c9c52990a840e29 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 4 Jun 2017 00:19:35 +0200 Subject: RequestModules(): Pretty print modules Parse the module XML with `ModuleParseXML()`. Take the resulting `[]Module` slice and use it to generate a string of the following format: ID Module 55555 R&D 77777 Sprint 1 222222 Sprint 2 This string is what gets printed to the console, which makes it rather easy to read the modules that are available for the given project and grab the appropriate ID to put into your config file. --- timetask/http.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/timetask/http.go b/timetask/http.go index 3ff6f4f..575ceae 100644 --- a/timetask/http.go +++ b/timetask/http.go @@ -1,6 +1,8 @@ package timetask import ( + "bytes" + "fmt" "io/ioutil" "net/http" "net/http/cookiejar" @@ -160,5 +162,18 @@ func RequestModules( } response_body := string(body) - return response_body, nil + 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 } -- cgit v1.2.3 From 6a62cc9aef3684ef0bd72e6ab79157a699b01c72 Mon Sep 17 00:00:00 2001 From: Teddy Wing Date: Sun, 4 Jun 2017 00:25:43 +0200 Subject: Update TODO --- TODO | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TODO b/TODO index 1c5c696..63d68fb 100644 --- a/TODO +++ b/TODO @@ -16,7 +16,7 @@ v Config (2017.06.03) v Make `PasswordCmd` work -- Request and list module IDs and names for a given project alias +v Request and list module IDs and names for a given project alias (2017.06.03) v Format float error ("700") (2017.06.03) -- cgit v1.2.3