Add option to fetch and store issues
This commit is contained in:
+11
-41
@@ -7,12 +7,7 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
@@ -21,17 +16,17 @@ var Api api
|
||||
|
||||
type api struct {}
|
||||
|
||||
func (api) CallApi(date string, from_time string, to_time string, item_id string, description string) error {
|
||||
if item_id == "break" || item_id == "lunch" || item_id == "pauze" { return nil }
|
||||
func (api) CallApi(date string, fromTime string, toTime string, itemID string, description string) error {
|
||||
if itemID == "break" || itemID == "lunch" || itemID == "pauze" { return nil }
|
||||
|
||||
if date == "" || from_time == "" || to_time == "" || item_id == "" {
|
||||
return fmt.Errorf("incomplete log entry: %s, %s-%s, %s, %s", date, from_time, to_time, item_id, description)
|
||||
if date == "" || fromTime == "" || toTime == "" || itemID == "" {
|
||||
return fmt.Errorf("incomplete log entry: %s, %s-%s, %s, %s", date, fromTime, toTime, itemID, description)
|
||||
}
|
||||
|
||||
time1, err := time.Parse("15:04", from_time)
|
||||
time1, err := time.Parse("15:04", fromTime)
|
||||
if err != nil { return fmt.Errorf("error parsing from_time: %s", err) }
|
||||
|
||||
time2, err := time.Parse("15:04", to_time)
|
||||
time2, err := time.Parse("15:04", toTime)
|
||||
if err != nil { return fmt.Errorf("error parsing to_time: %s", err) }
|
||||
|
||||
// Convert local timezone to UTC time
|
||||
@@ -41,42 +36,17 @@ func (api) CallApi(date string, from_time string, to_time string, item_id string
|
||||
|
||||
duration := time2.Sub(time1)
|
||||
seconds := int(duration.Seconds())
|
||||
if seconds < 0 { return fmt.Errorf("from_time is later than to_time: %s > %s", from_time, to_time) }
|
||||
if seconds < 0 { return fmt.Errorf("from_time is later than to_time: %s > %s", fromTime, toTime) }
|
||||
|
||||
var url string = base_url + "/rest/api/2/issue/" + item_id + "/worklog"
|
||||
var url string = baseUrl + "/rest/api/2/issue/" + itemID + "/worklog"
|
||||
|
||||
data := map[string]string{
|
||||
"comment": description,
|
||||
"started": fmt.Sprintf("%sT%s:00.000+0000", date, from_time), // "2021-01-17T12:34:00.000+0000",
|
||||
"started": fmt.Sprintf("%sT%s:00.000+0000", date, fromTime), // "2021-01-17T12:34:00.000+0000",
|
||||
"timeSpentSeconds": strconv.Itoa(seconds),
|
||||
}
|
||||
|
||||
json_data, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling JSON: %s", err)
|
||||
}
|
||||
_, err = Request(url, data, 201) // "Created"
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(json_data))
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating request: %s", err)
|
||||
}
|
||||
|
||||
auth := username + ":" + password
|
||||
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil { return fmt.Errorf("error making request: %s", err) }
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil { return fmt.Errorf("error reading response body: %s", err) }
|
||||
|
||||
if resp.Status != "201 Created" {
|
||||
return fmt.Errorf("invalid Jira request:\n%s", string(body))
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type IssueData struct {
|
||||
totalIssues []issueResponse
|
||||
}
|
||||
|
||||
// Constructor
|
||||
func MakeIssueData() IssueData {
|
||||
return IssueData {}
|
||||
}
|
||||
|
||||
func (self *IssueData) GenerateIssuesFile(path string) {
|
||||
// Safety check
|
||||
verify(filepath.Base(path) != "worklog.org", "protected from overwriting worklog file!")
|
||||
|
||||
// Get all issues of the active sprint, and its subtasks
|
||||
self.fetchIssues(0)
|
||||
|
||||
// Store the issues
|
||||
outputFile, err := os.Create(path)
|
||||
assert(err)
|
||||
defer outputFile.Close()
|
||||
writer := bufio.NewWriter(outputFile)
|
||||
defer writer.Flush()
|
||||
self.writeIssues(writer)
|
||||
|
||||
// Clear the results
|
||||
self.totalIssues = self.totalIssues[:0]
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
type issuesResponse struct {
|
||||
StartAt int `json:"startAt"`
|
||||
MaxResults int `json:"maxResults"`
|
||||
Total int `json:"total"`
|
||||
Issues []issueResponse `json:"issues"`
|
||||
}
|
||||
|
||||
type issueResponse struct {
|
||||
Key string `json:"key"`
|
||||
Fields fieldsResponse `json:"fields"`
|
||||
}
|
||||
|
||||
type fieldsResponse struct {
|
||||
Summary string `json:"summary"`
|
||||
SubTasks []subTaskResponse `json:"subtasks"`
|
||||
}
|
||||
|
||||
type subTaskResponse struct {
|
||||
Key string `json:"key"`
|
||||
Fields subTaskFieldsResponse `json:"fields"`
|
||||
}
|
||||
|
||||
type subTaskFieldsResponse struct {
|
||||
Summary string `json:"summary"`
|
||||
}
|
||||
|
||||
// -----------------------------------------
|
||||
|
||||
func (self *IssueData) fetchIssues(startAt int) error {
|
||||
var url string = baseUrl + "/rest/api/2/search"
|
||||
var maxResults int = 100
|
||||
data := map[string]interface{}{
|
||||
"fields": []string{ "key", "summary", "subtasks" },
|
||||
"jql": `project = "` + projectName + `" AND sprint IN openSprints() AND issuetype != "Sub-task" ORDER BY created ASC`,
|
||||
"maxResults": maxResults,
|
||||
"startAt": startAt,
|
||||
}
|
||||
|
||||
body, err := Request(url, data, 200) // "OK"
|
||||
if err != nil { return err }
|
||||
|
||||
var result issuesResponse
|
||||
err = json.Unmarshal(body, &result)
|
||||
if err != nil { fmt.Println("NOPE!"); return err }
|
||||
|
||||
// Add fetched issues
|
||||
self.totalIssues = append(self.totalIssues, result.Issues...)
|
||||
|
||||
// Pagination, if more results
|
||||
if startAt + maxResults < result.Total {
|
||||
self.fetchIssues(startAt + maxResults)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self* IssueData) writeIssues(writer *bufio.Writer) {
|
||||
// Issues
|
||||
writer.WriteString(".\n")
|
||||
var count int = len(self.totalIssues)
|
||||
for i, issue := range(self.totalIssues) {
|
||||
if i == count - 1 {
|
||||
writer.WriteString("└")
|
||||
} else {
|
||||
writer.WriteString("├")
|
||||
}
|
||||
writer.WriteString("── ")
|
||||
writer.WriteString(issue.Key)
|
||||
writer.WriteString(" ")
|
||||
writer.WriteString(issue.Fields.Summary)
|
||||
writer.WriteString("\n")
|
||||
|
||||
// Subtasks
|
||||
var subtaskCount int = len(issue.Fields.SubTasks)
|
||||
for j, subtask := range(issue.Fields.SubTasks) {
|
||||
// Last issue
|
||||
if i == count - 1 {
|
||||
writer.WriteString(" ")
|
||||
} else {
|
||||
writer.WriteString("│ ")
|
||||
}
|
||||
// Last subtask
|
||||
if j == subtaskCount - 1 {
|
||||
writer.WriteString("└")
|
||||
} else {
|
||||
writer.WriteString("├")
|
||||
}
|
||||
writer.WriteString("── ")
|
||||
writer.WriteString(subtask.Key)
|
||||
writer.WriteString(" ")
|
||||
writer.WriteString(subtask.Fields.Summary)
|
||||
writer.WriteString("\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package src
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func Request[T ~map[string]string | ~map[string]interface{}](url string, data T, status int) ([]byte, error) {
|
||||
jsonData, err := json.Marshal(data)
|
||||
if err != nil { return nil, fmt.Errorf("error marshaling JSON: %s", err) }
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil { return nil, fmt.Errorf("error creating request: %s", err) }
|
||||
|
||||
auth := username + ":" + password
|
||||
authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth))
|
||||
req.Header.Set("Authorization", authHeader)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil { return nil, fmt.Errorf("error making request: %s", err) }
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil { return nil, fmt.Errorf("error reading response body: %s", err) }
|
||||
|
||||
if resp.StatusCode != status {
|
||||
return nil, fmt.Errorf("invalid Jira request:\n%s", string(body))
|
||||
}
|
||||
|
||||
return body, nil
|
||||
}
|
||||
+2
-1
@@ -9,4 +9,5 @@ package src
|
||||
var username string = "" // email
|
||||
var password string = "" // API key
|
||||
|
||||
var base_url string = "" // base URL
|
||||
var baseUrl string = ""
|
||||
var projectName string = ""
|
||||
|
||||
Reference in New Issue
Block a user