Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for redmine issue tracker #28

Merged
merged 1 commit into from
Jul 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ See [How it works](#how-it-works) for more info.
* [Gitlab](#gitlab)
* [Jira](#jira)
* [Pivotal Tracker](#pivotal-tracker)
* [Redmine](#redmine)
- [Supported Programming Languages](#supported-programming-languages)
- [Ignored Files & Directories](#ignored-files--directories)
- [Supported Output Formats](#supported-output-formats)
Expand Down Expand Up @@ -170,7 +171,7 @@ auth:
type: apitoken
```

The first time you run the application, it will ask for your [API Token](https://www.pivotaltracker.com/help/articles/api_token/):
The first time you run the application, it will ask for your [API Token](https://www.pivotaltracker.com/help/articles/api_token/).

After you've specified it, it will store it in the auth tokens cache for subsequent executions. See the [Authentication](#authentication) section for more info.

Expand All @@ -182,6 +183,25 @@ For example:
// TODO #123456: This one is not
```

## Redmine
To integrate with a redmine issue tracker project, specify the origin of your installation (without project path) and the `REDMINE` issue tracker in your `.todocheck.yaml` configuration.

You should also specify the `apitoken` as an auth type if you have authentication enabled on your server:
```
origin: https://redmine.mycorp.com
issue_tracker: REDMINE
auth:
type: apitoken
```

Alternatively, if no authentication is required, don't include the `auth` section.

The first time you run the application, it will ask for your [API Token](https://www.redmine.org/projects/redmine/wiki/rest_api#Authentication).

After you've specified it, it will store it in the auth tokens cache for subsequent executions. See the [Authentication](#authentication) section for more info.

In order to integrate todocheck with your redmine server, you'll need to enable the server's rest API and, optionally, enable authentication - [See Docs](https://www.redmine.org/projects/redmine/wiki/rest_api#Authentication).


# Supported Programming Languages
Currently, todocheck has parsers for three different types of comments:
Expand Down
3 changes: 3 additions & 0 deletions authmanager/authmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const (
githubAPITokenMsg = "Please go to https://github.com/settings/tokens, create a read-only access token & paste it here:\nToken: "
gitlabAPITokenMsg = "Please go to https://gitlab.com/profile/personal_access_tokens, create a read-only access token & paste it here:\nToken: "
pivotalAPITokenMsg = "Please go to https://www.pivotaltracker.com/profile, create a new API token & paste it here:\nToken: "
redmineAPITokenMsg = "Please go to %s/my/account, create a new API token & paste it here:\nToken: "

authTokenEnvVariable = "TODOCHECK_AUTH_TOKEN"
)
Expand Down Expand Up @@ -43,6 +44,8 @@ func acquireAPIToken(cfg *config.Local) error {
msg = gitlabAPITokenMsg
} else if cfg.IssueTracker == config.IssueTrackerPivotal {
msg = pivotalAPITokenMsg
} else if cfg.IssueTracker == config.IssueTrackerRedmine {
msg = fmt.Sprintf(redmineAPITokenMsg, cfg.Origin)
} else {
panic("attempt to acquire token for unsupported issue tracker " + cfg.IssueTracker)
}
Expand Down
8 changes: 8 additions & 0 deletions authmanager/authmiddleware/authmiddleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ func For(cfg *config.Local) Func {
return gitlabAPITokenMiddleware(cfg.Auth.Token)
} else if cfg.IssueTracker == config.IssueTrackerPivotal && cfg.Auth.Type == config.AuthTypeAPIToken {
return pivotalTrackerAPITokenMiddleware(cfg.Auth.Token)
} else if cfg.IssueTracker == config.IssueTrackerRedmine && cfg.Auth.Type == config.AuthTypeAPIToken {
return redmineAPITokenMiddleware(cfg.Auth.Token)
}

panic("couldn't derive authentication middleware based on the given local configuration")
Expand Down Expand Up @@ -51,6 +53,12 @@ func pivotalTrackerAPITokenMiddleware(token string) Func {
}
}

func redmineAPITokenMiddleware(token string) Func {
return func(r *http.Request) {
r.Header.Add("X-Redmine-API-Key", token)
}
}

func assertInvariant(condition bool, msg string) {
if !condition {
panic(msg)
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const (
IssueTrackerGithub = "GITHUB"
IssueTrackerGitlab = "GITLAB"
IssueTrackerPivotal = "PIVOTAL_TRACKER"
IssueTrackerRedmine = "REDMINE"
)

// Local todocheck configuration struct definition
Expand Down
2 changes: 1 addition & 1 deletion fetcher/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func NewFetcher(origin string, tracker config.IssueTracker, authMw authmiddlewar

// Fetch a task's status based on task ID
func (f *Fetcher) Fetch(taskID string) (taskstatus.TaskStatus, error) {
req, err := http.NewRequest("GET", f.origin+taskID, nil)
req, err := http.NewRequest("GET", f.origin+issuetracker.TaskURLSuffixFor(taskID, f.tracker), nil)
if err != nil {
return taskstatus.None, fmt.Errorf("failed creating new GET request: %w", err)
}
Expand Down
18 changes: 17 additions & 1 deletion issuetracker/types.go → issuetracker/issuetracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,30 @@ func TaskFor(issueTracker config.IssueTracker) models.Task {
return &models.GitlabTask{}
case config.IssueTrackerPivotal:
return &models.PivotalTrackerTask{}
case config.IssueTrackerRedmine:
return &models.RedmineTask{}
default:
return nil
}
}

// TaskURLSuffixFor the given issue tracker. Returns the appropriate task ID to append to rest api request.
// In most use-cases, the taskID is returned as-is as it is simply appended to the issue tracker URL origin (e.g. issuetracker.com/issues/{taskID}.
// However, some issue trackers might expect the taskID to be appended in a special format
func TaskURLSuffixFor(taskID string, issueTracker config.IssueTracker) string {
switch issueTracker {
case config.IssueTrackerRedmine:
return taskID + ".json"
default:
return taskID
}
}

// BaseURLFor returns the task-fetching base url given the issue tracker type and the site origin
func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error) {
switch issueTracker {
case config.IssueTrackerJira:
return origin + "/rest/api/latest/issue/", nil
return fmt.Sprintf("%s/rest/api/latest/issue/", origin), nil
case config.IssueTrackerGithub:
tokens := common.RemoveEmptyTokens(strings.Split(origin, "/"))
if tokens[0] == "github.com" {
Expand Down Expand Up @@ -61,6 +75,8 @@ func BaseURLFor(issueTracker config.IssueTracker, origin string) (string, error)
}

return fmt.Sprintf("%s//www.pivotaltracker.com/services/v5/projects/%s/stories/", scheme, project), nil
case config.IssueTrackerRedmine:
return fmt.Sprintf("%s/issues/", origin), nil
default:
return "", errors.New("unknown issue tracker " + string(issueTracker))
}
Expand Down
2 changes: 1 addition & 1 deletion issuetracker/models/pivotaltracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ type PivotalTrackerTask struct {
CurrentState string `json:"current_state"`
}

// GetStatus of picotal tracker task, based on underlying structure
// GetStatus of pivotal tracker task, based on underlying structure
func (t *PivotalTrackerTask) GetStatus() taskstatus.TaskStatus {
switch t.CurrentState {
case "finished":
Expand Down
28 changes: 28 additions & 0 deletions issuetracker/models/redmine.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package models

import "github.com/preslavmihaylov/todocheck/issuetracker/taskstatus"

// RedmineTask model
type RedmineTask struct {
Issue struct {
Status struct {
Name string `json:"name"`
} `json:"status"`
} `json:"issue"`
}

// GetStatus of pivotal tracker task, based on underlying structure
func (t *RedmineTask) GetStatus() taskstatus.TaskStatus {
switch t.Issue.Status.Name {
case "Resolved":
fallthrough
case "Closed":
fallthrough
case "Feedback":
fallthrough
case "Rejected":
return taskstatus.Closed
default:
return taskstatus.Open
}
}