Skip to content

Commit 80329f9

Browse files
author
Thomas Peetz
authored
Merge pull request #9 from tpeetz/redmine-api
Add Redmine API
2 parents d68114a + 9fe5f54 commit 80329f9

File tree

12 files changed

+258
-3
lines changed

12 files changed

+258
-3
lines changed

pkg/server/gitserver.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package server
33
import (
44
"github.com/tpeetz/pull-task/pkg/types/github"
55
"github.com/tpeetz/pull-task/pkg/types/gitlab"
6+
"github.com/tpeetz/pull-task/pkg/types/redmine"
67
)
78

89
// GitServer definees the methods for server (Gitlab, Github, Redmine)
@@ -33,7 +34,7 @@ func NewGitServer(details map[string]interface{}) (GitServer, error) {
3334
case "github":
3435
server = &github.Server{}
3536
case "redmine":
36-
return nil, &UnkownServiceTypeError{"redmine"}
37+
server = &redmine.Server{}
3738
default:
3839
return nil, &UnkownServiceTypeError{service.(string)}
3940
}

pkg/server/gitserver_test.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/tpeetz/pull-task/pkg/types/github"
88
"github.com/tpeetz/pull-task/pkg/types/gitlab"
9+
"github.com/tpeetz/pull-task/pkg/types/redmine"
910
)
1011

1112
var (
@@ -19,8 +20,9 @@ var (
1920
ghServer = &github.Server{URL: "https://api.github.com"}
2021
redmineConfig = map[string]interface{}{
2122
"service": "redmine",
23+
//"limit": 120,
2224
}
23-
//rmServer = &GitServer{url: "https://redmine.com", token: "secretToken"}
25+
rmServer = &redmine.Server{URL: "https://redmine.example.com", Limit: 120}
2426
unknownConfig = map[string]interface{}{
2527
"service": "unknown",
2628
}
@@ -38,7 +40,8 @@ func TestNewGitServer(t *testing.T) {
3840
}{
3941
{"gitlab", args{gitlabConfig}, glServer, false},
4042
{"github", args{githubConfig}, ghServer, false},
41-
{"redmine", args{redmineConfig}, nil, true},
43+
{"redmine", args{redmineConfig}, rmServer, false},
44+
{"redmine-limit", args{map[string]interface{}{"service": "redmine", "limit": 99}}, &redmine.Server{URL: "https://redmine.example.com", Limit: 99}, false},
4245
{"unknown", args{unknownConfig}, nil, true},
4346
}
4447
for _, tt := range tests {

pkg/types/redmine/customfield.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package redmine
2+
3+
import "fmt"
4+
5+
// CustomField represents a custom field of issue in Redmine.
6+
type CustomField struct {
7+
ID int `json:"id"`
8+
Name string `json:"name"`
9+
Value string `json:"value"`
10+
}
11+
12+
func (customField *CustomField) String() string {
13+
return fmt.Sprintf("CustomField: %d=%s", customField.ID, customField.Name)
14+
}

pkg/types/redmine/issue.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package redmine
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
)
7+
8+
// Issue represents issue in Redmine.
9+
type Issue struct {
10+
ID int `json:"id"`
11+
Project Project `json:"project"`
12+
Tracker Tracker `json:"tracker"`
13+
Status Status `json:"status"`
14+
Priority Priority `json:"priority"`
15+
Author User `json:"author"`
16+
Assigned User `json:"assigned_to"`
17+
Parent ParentIssue `json:"parent"`
18+
Subject string `json:"subject"`
19+
Description string `json:"description,omitempty"`
20+
StartDate string `json:"start_date,omitempty"`
21+
DueDate string `json:"due_date,omitempty"`
22+
DoneRatio int `json:"done_ratio"`
23+
Estimated json.Number `json:"estimated_hours"`
24+
Created string `json:"created_on,omitempty"`
25+
Updated string `json:"updated_on,omitempty"`
26+
CustomFields []CustomField `json:"custom_fields,omitempty"`
27+
}
28+
29+
func (issue Issue) String() string {
30+
return fmt.Sprintf("Issue %d: %s\n %s\n Start : %s\n Due : %s\n %s\n", issue.ID, issue.Subject, issue.Project, issue.StartDate, issue.DueDate, issue.Status)
31+
}
32+
33+
// Short returns ID and subject of issue.
34+
func (issue Issue) Short() string {
35+
return fmt.Sprintf("Issue %d: %s", issue.ID, issue.Subject)
36+
}
37+
38+
// SingleIssue represents a JSON answer of a single issue in Redmine.
39+
type SingleIssue struct {
40+
Issue Issue `json:"issue"`
41+
}

pkg/types/redmine/issuelist.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package redmine
2+
3+
// IssueList represents the list of issue in Redmine.
4+
type IssueList struct {
5+
Issues []Issue `json:"issues"`
6+
TotalCount int `json:"total_count"`
7+
Offset int `json:"offset"`
8+
Limit int `json:"limit"`
9+
}

pkg/types/redmine/parentissue.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package redmine
2+
3+
import "fmt"
4+
5+
// ParentIssue holds the ID of the parent issue of the current one.
6+
type ParentIssue struct {
7+
ID int `json:"id"`
8+
}
9+
10+
func (parent *ParentIssue) String() string {
11+
return fmt.Sprintf("Parent Issue: %d", parent.ID)
12+
}

pkg/types/redmine/priority.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package redmine
2+
3+
import "fmt"
4+
5+
// Priority represents priority of issue in Redmine.
6+
type Priority struct {
7+
ID int `json:"id"`
8+
Name string `json:"name"`
9+
}
10+
11+
func (priority Priority) String() string {
12+
return fmt.Sprintf("Priority: %s", priority.Name)
13+
}

pkg/types/redmine/project.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package redmine
2+
3+
import "fmt"
4+
5+
// Project represents project in Redmine.
6+
type Project struct {
7+
ID int `json:"id"`
8+
Name string `json:"name"`
9+
}
10+
11+
func (project Project) String() string {
12+
return fmt.Sprintf("Project: %s", project.Name)
13+
}

pkg/types/redmine/server.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package redmine
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
)
8+
9+
// Server represents a instance on an Redmine server.
10+
type Server struct {
11+
URL string
12+
Token string
13+
Limit int
14+
}
15+
16+
func (server *Server) String() string {
17+
return fmt.Sprintf("Redmine server: %v", server.URL)
18+
}
19+
20+
// Configure reads the configuration details from map and sets the server instance.
21+
func (server *Server) Configure(details map[string]interface{}) error {
22+
url, ok := details["domain"]
23+
if ok {
24+
serverURL, correctType := url.(string)
25+
if correctType {
26+
server.URL = serverURL
27+
}
28+
} else {
29+
server.URL = "https://redmine.example.com"
30+
}
31+
token, ok := details["token"]
32+
if ok {
33+
serverToken, correctType := token.(string)
34+
if correctType {
35+
server.Token = serverToken
36+
}
37+
}
38+
limit, ok := details["limit"]
39+
if ok {
40+
serverLimit, correctType := limit.(int)
41+
if correctType {
42+
server.Limit = serverLimit
43+
}
44+
} else {
45+
server.Limit = 120
46+
}
47+
return nil
48+
}
49+
50+
// LoadIssues gets issues from Redmine server.
51+
func (server *Server) LoadIssues() error {
52+
fmt.Printf("Redmine load issues from %s\n", server.URL)
53+
issuesURL := fmt.Sprintf("%s/issues.json?limit=%d", server.URL, server.Limit)
54+
request, err := http.NewRequest("GET", issuesURL, nil)
55+
if err != nil {
56+
fmt.Printf("creation of request failed: %v\n", err)
57+
return err
58+
}
59+
request.Header.Set("Content-Type", "application/json")
60+
request.Header.Set("X-Redmine-API-Key", server.Token)
61+
client := &http.Client{}
62+
response, err := client.Do(request)
63+
if err != nil {
64+
fmt.Printf("The HTTP request failed with error %s\n", err)
65+
return err
66+
}
67+
defer response.Body.Close()
68+
var issueList IssueList
69+
if err := json.NewDecoder(response.Body).Decode(&issueList); err != nil {
70+
fmt.Printf("Response could not parsed as JSON - %v\n", err)
71+
return err
72+
}
73+
fmt.Printf("Issue List:\n%v\n", issueList)
74+
return nil
75+
}
76+
77+
// LoadProjects gets projects from Redmine server.
78+
func (server *Server) LoadProjects() error {
79+
fmt.Printf("Gitlab load projects from %s\n", server.URL)
80+
projectsURL := fmt.Sprintf("%s/%s/projects?per_page=%d", server.URL, "api/v4", 120)
81+
request, err := http.NewRequest("GET", projectsURL, nil)
82+
if err != nil {
83+
fmt.Printf("creation of request failed: %v\n", err)
84+
return err
85+
}
86+
request.Header.Set("Content-Type", "application/json")
87+
request.Header.Set("PRIVATE-TOKEN", server.Token)
88+
client := &http.Client{}
89+
response, err := client.Do(request)
90+
if err != nil {
91+
fmt.Printf("The HTTP request failed with error %s\n", err)
92+
return err
93+
}
94+
defer response.Body.Close()
95+
var projectList []Project
96+
if err := json.NewDecoder(response.Body).Decode(&projectList); err != nil {
97+
fmt.Printf("Response could not parsed as JSON - %v\n", err)
98+
return err
99+
}
100+
fmt.Printf("Project List:\n%v\n", projectList)
101+
return nil
102+
}

pkg/types/redmine/status.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package redmine
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
)
7+
8+
// Status represents the issue status in Redmine.
9+
type Status struct {
10+
ID int `json:"id"`
11+
Name string `json:"name"`
12+
}
13+
14+
func (status Status) String() string {
15+
return fmt.Sprintf("Status : %s", status.Name)
16+
}
17+
18+
// Convert removes whitespace from status name.
19+
func (status Status) Convert() string {
20+
return strings.Replace(status.Name, " ", "", 1)
21+
}

0 commit comments

Comments
 (0)