Compare commits
3 Commits
cd3ca50a22
...
f02cbaebf7
Author | SHA1 | Date |
---|---|---|
Riyyi | f02cbaebf7 | 4 months ago |
Riyyi | 3babed3def | 4 months ago |
Riyyi | 3a8657e770 | 4 months ago |
7 changed files with 228 additions and 52 deletions
@ -1,21 +1,45 @@ |
|||||||
* Worklog |
#+TITLE: Worklog |
||||||
|
#+AUTHOR: Riyyi |
||||||
|
#+LANGUAGE: en |
||||||
|
#+OPTIONS: toc:nil |
||||||
|
|
||||||
Register worklog entries to the Jira API. |
Register worklog entries to the Jira API. |
||||||
|
|
||||||
* Download |
** Getting started |
||||||
|
|
||||||
** Clone |
*** Clone |
||||||
|
|
||||||
#+BEGIN_SRC sh |
#+BEGIN_SRC sh |
||||||
$ git clone https://github.com/riyyi/worklog |
$ git clone https://github.com/riyyi/worklog |
||||||
#+END_SRC |
#+END_SRC |
||||||
|
|
||||||
* Build instructions |
*** Build instructions |
||||||
|
|
||||||
#+BEGIN_SRC sh |
#+BEGIN_SRC sh |
||||||
$ go build |
$ go build |
||||||
#+END_SRC |
#+END_SRC |
||||||
|
|
||||||
* Gitignore |
*** Usage |
||||||
|
|
||||||
git update-index --assume-unchanged src/secrets.go |
#+BEGIN_SRC sh |
||||||
|
$ worklog --help |
||||||
|
worklog - process a worklog file |
||||||
|
|
||||||
|
Usage: worklog [--decl MONTH] [--process] [--issues] FILE |
||||||
|
|
||||||
|
Positional arguments: |
||||||
|
FILE the file to perform the action on |
||||||
|
|
||||||
|
Options: |
||||||
|
--decl MONTH, -d MONTH |
||||||
|
Generate travel declaration table |
||||||
|
--process, -p Process specified file and call Jira API |
||||||
|
--issues, -i Store issues in specified file |
||||||
|
--help, -h display this help and exit |
||||||
|
#+END_SRC |
||||||
|
|
||||||
|
** Gitignore |
||||||
|
|
||||||
|
#+BEGIN_SRC sh |
||||||
|
$ git update-index --assume-unchanged src/secrets.go |
||||||
|
#+END_SRC |
||||||
|
@ -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 |
||||||
|
} |
Loading…
Reference in new issue