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. |
||||
|
||||
* Download |
||||
** Getting started |
||||
|
||||
** Clone |
||||
*** Clone |
||||
|
||||
#+BEGIN_SRC sh |
||||
$ git clone https://github.com/riyyi/worklog |
||||
#+END_SRC |
||||
|
||||
* Build instructions |
||||
*** Build instructions |
||||
|
||||
#+BEGIN_SRC sh |
||||
$ go build |
||||
#+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