Skip to content

Commit

Permalink
feat: Redmine Support
Browse files Browse the repository at this point in the history
Redmine issue tracker support added
  • Loading branch information
Hakan Uygun committed Nov 14, 2021
1 parent 336dae0 commit afb929c
Show file tree
Hide file tree
Showing 20 changed files with 556 additions and 3 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The changelog can:
- Be stored to file, like `CHANGELOG.md`. There are some templates used for testing available [here](/src/test/resources/templatetest).
- Or, just rendered to a `String`.

It can integrate with Jira and/or GitHub to retrieve the title of issues.
It can integrate with Jira, Redmine, GitLab and/or GitHub to retrieve the title of issues.

## Usage

Expand Down
36 changes: 36 additions & 0 deletions src/main/java/se/bjurr/gitchangelog/api/GitChangelogApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,42 @@ public GitChangelogApi withJiraUsername(final String string) {
return this;
}

/**
* Pattern to recognize Redmine:s. <code>#([0-9]+)</code>
*/
public GitChangelogApi withRedmineIssuePattern(final String redmineIssuePattern) {
this.settings.setRedmineIssuePattern(redmineIssuePattern);
return this;
}

/** Authenticate to Redmine. */
public GitChangelogApi withRedminePassword(final String string) {
this.settings.setRedminePassword(string);
return this;
}

/** Authenticate to Redmine with API_KEY */
public GitChangelogApi withRedmineToken(final String string) {
this.settings.setRedmineToken(string);
return this;
}

/**
* URL pointing at your Redmine server. When configured, the {@link Issue#getTitle()} will be
* populated with title from Redmine.<br>
* <code>https://redmineserver/</code>
*/
public GitChangelogApi withRedmineServer(final String redmineServer) {
this.settings.setRedmineServer(redmineServer);
return this;
}

/** Authenticate to Redmine. */
public GitChangelogApi withRedmineUsername(final String string) {
this.settings.setRedmineUsername(string);
return this;
}

/**
* This is a "virtual issue" that is added to {@link Changelog#getIssues()}. It contains all
* commits that has no issue in the commit comment. This could be used as a "wall of shame"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public final class GitChangelogApiConstants {
public static final String DEFAULT_GITHUB_ISSUE_PATTERN = "#([0-9]+)";
public static final String DEFAULT_GITLAB_ISSUE_PATTERN = "#([0-9]+)";
public static final String DEFAULT_JIRA_ISSUE_PATTEN = "\\b[a-zA-Z]([a-zA-Z]+)-([0-9]+)\\b";
public static final String DEFAULT_REDMINE_ISSUE_PATTEN = "#([0-9]+)";
public static final String DEFAULT_MINOR_PATTERN = "^[Ff]eat.*";
public static final String DEFAULT_PATCH_PATTERN = null;

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/se/bjurr/gitchangelog/api/model/Issue.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.GITHUB;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.GITLAB;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.JIRA;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.REDMINE;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.NOISSUE;
import static se.bjurr.gitchangelog.internal.util.Preconditions.checkNotNull;
import static se.bjurr.gitchangelog.internal.util.Preconditions.checkState;
Expand Down Expand Up @@ -95,6 +96,10 @@ public boolean isJira() {
return this.issueType == JIRA;
}

public boolean isRedmine() {
return this.issueType == REDMINE;
}

public boolean isGitHub() {
return this.issueType == GITHUB;
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/se/bjurr/gitchangelog/api/model/IssueType.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.GITHUB;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.GITLAB;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.JIRA;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.REDMINE;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.NOISSUE;
import static se.bjurr.gitchangelog.internal.util.Preconditions.checkNotNull;
import static se.bjurr.gitchangelog.internal.util.Preconditions.checkState;
Expand Down Expand Up @@ -34,6 +35,10 @@ public boolean isJira() {
return this.type == JIRA;
}

public boolean isRedmine() {
return this.type == REDMINE;
}

public boolean isGitHub() {
return this.type == GITHUB;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package se.bjurr.gitchangelog.internal.integrations.redmine;

import java.io.UnsupportedEncodingException;
import java.util.Base64;
import java.util.Map;
import java.util.Optional;
import se.bjurr.gitchangelog.api.exceptions.GitChangelogIntegrationException;
import se.bjurr.gitchangelog.internal.integrations.rest.RestClient;

public class DefaultRedmineClient extends RedmineClient {

private RestClient client;

public DefaultRedmineClient(final String api) {
super(api);
this.client = new RestClient();
}

@Override
public RedmineClient withBasicCredentials(final String username, final String password) {
this.client = this.client.withBasicAuthCredentials(username, password);
return this;
}

@Override
public RedmineClient withTokenCredentials(final String token) {
String authToken;
try {
authToken = Base64.getEncoder().encodeToString((token + ":changelog" ).getBytes("UTF-8"));
} catch (final UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
this.client = this.client.withTokenAuthCredentials(authToken);
return this;
}

@Override
public RedmineClient withHeaders(final Map<String, String> headers) {
this.client = this.client.withHeaders(headers);
return this;
}

@Override
public Optional<RedmineIssue> getIssue(final String issue) throws GitChangelogIntegrationException {
final String endpoint = this.getEndpoint(issue);
final Optional<String> json = this.client.get(endpoint);
if (json.isPresent()) {
final String jsonString = json.get();
try {
final RedmineIssue redmineIssue = this.toRedmineIssue(issue, jsonString);
return Optional.of(redmineIssue);
} catch (final Exception e) {
throw new GitChangelogIntegrationException("Unable to parse:\n" + jsonString, e);
}
}
return Optional.empty();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package se.bjurr.gitchangelog.internal.integrations.redmine;

import static com.jayway.jsonpath.JsonPath.read;

import java.util.Map;
import java.util.Optional;

import se.bjurr.gitchangelog.api.exceptions.GitChangelogIntegrationException;

public abstract class RedmineClient {
private final String api;

public RedmineClient(final String api) {
if (api.endsWith("/")) {
this.api = api.substring(0, api.length() - 1);
} else {
this.api = api;
}
}

public String getApi() {
return this.api;
}

protected String getEndpoint(final String issue) {
final String issueNo = getIssueNumber(issue);
final String endpoint = this.api + "/issues/" + issueNo + ".json";
return endpoint;
}

protected RedmineIssue toRedmineIssue(final String issue, final String json) {
final String issueNo = getIssueNumber(issue);
final String title = read(json, "$.issue.subject");
final String description = read(json, "$.issue.description");
final String type = read(json, "$.issue.tracker.name");
final String link = this.api + "/issues/" + issueNo;

final RedmineIssue redmineIssue = new RedmineIssue(title, description, link, issue, type);
return redmineIssue;
}

protected String getIssueNumber( String issue ){
return issue.startsWith("#") ? issue.substring(1) : issue;
}

public abstract RedmineClient withBasicCredentials(String username, String password);

public abstract RedmineClient withTokenCredentials(String token);

public abstract RedmineClient withHeaders(Map<String, String> headers);

public abstract Optional<RedmineIssue> getIssue(String matched) throws GitChangelogIntegrationException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package se.bjurr.gitchangelog.internal.integrations.redmine;

public class RedmineClientFactory {

private static RedmineClient redmineClient;

public static void reset() {
redmineClient = null;
}

/**
* The Bitbucket Server plugin uses this method to inject the Atlassian Client.
*/
public static void setRedmineClient(final RedmineClient redmineClient) {
RedmineClientFactory.redmineClient = redmineClient;
}

public static RedmineClient createRedmineClient(final String apiUrl) {
if (redmineClient != null) {
return redmineClient;
}
return new DefaultRedmineClient(apiUrl);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package se.bjurr.gitchangelog.internal.integrations.redmine;

public class RedmineIssue {
private final String title;
private final String link;
private final String issue;
private final String issueType;
private final String description;


public RedmineIssue(String title, String description, String link, String issue, String issueType ) {
this.title = title;
this.link = link;
this.issue = issue;
this.issueType = issueType;
this.description = description;
}

public String getIssue() {
return issue;
}

public String getLink() {
return link;
}

public String getTitle() {
return title;
}

public String getIssueType() {
return issueType;
}

public String getDescription() {

return description;
}

@Override
public String toString() {
return "RedmineIssue [title=" + title + ", link=" + link + ", issue=" + issue + ", issueType=" + issueType + "]";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.GITHUB;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.GITLAB;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.JIRA;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.REDMINE;
import static se.bjurr.gitchangelog.internal.settings.SettingsIssueType.NOISSUE;

import java.util.ArrayList;
Expand All @@ -26,6 +27,9 @@
import se.bjurr.gitchangelog.internal.integrations.jira.JiraClient;
import se.bjurr.gitchangelog.internal.integrations.jira.JiraClientFactory;
import se.bjurr.gitchangelog.internal.integrations.jira.JiraIssue;
import se.bjurr.gitchangelog.internal.integrations.redmine.RedmineClient;
import se.bjurr.gitchangelog.internal.integrations.redmine.RedmineClientFactory;
import se.bjurr.gitchangelog.internal.integrations.redmine.RedmineIssue;
import se.bjurr.gitchangelog.internal.model.ParsedIssue;
import se.bjurr.gitchangelog.internal.settings.IssuesUtil;
import se.bjurr.gitchangelog.internal.settings.Settings;
Expand Down Expand Up @@ -56,8 +60,9 @@ public List<ParsedIssue> parseForIssues(final boolean useIntegrationIfConfigured

final GitHubHelper gitHubHelper = useIntegrationIfConfigured ? this.createGitHubClient() : null;
final JiraClient jiraClient = useIntegrationIfConfigured ? this.createJiraClient() : null;
final RedmineClient redmineClient = useIntegrationIfConfigured ? this.createRedmineClient() : null;
final GitLabClient gitLabClient = useIntegrationIfConfigured ? this.createGitLabClient() : null;

final List<SettingsIssue> patterns = new IssuesUtil(this.settings).getIssues();

for (final GitCommit gitCommit : this.commits) {
Expand All @@ -80,6 +85,8 @@ public List<ParsedIssue> parseForIssues(final boolean useIntegrationIfConfigured
this.createParsedIssue(gitLabClient, projectName, issuePattern, matchedIssue);
} else if (issuePattern.getType() == JIRA) {
parsedIssue = this.createParsedIssue(jiraClient, issuePattern, matchedIssue);
} else if (issuePattern.getType() == REDMINE) {
parsedIssue = this.createParsedIssue(redmineClient, issuePattern, matchedIssue);
} else {
parsedIssue = this.createParsedIssue(issuePattern, issueMatcher, matchedIssue);
}
Expand Down Expand Up @@ -183,6 +190,23 @@ private JiraClient createJiraClient() {
return jiraClient;
}

private RedmineClient createRedmineClient() {
RedmineClient redmineClient = null;
if (this.settings.getRedmineServer().isPresent()) {
redmineClient = RedmineClientFactory.createRedmineClient(this.settings.getRedmineServer().get());
if (this.settings.getRedmineUsername().isPresent()) {
redmineClient.withBasicCredentials(
this.settings.getRedmineUsername().get(), this.settings.getRedminePassword().get());
} else if (this.settings.getRedmineToken().isPresent()) {
redmineClient.withTokenCredentials(this.settings.getRedmineToken().get());
}
if (this.settings.getExtendedRestHeaders() != null) {
redmineClient.withHeaders(this.settings.getExtendedRestHeaders());
}
}
return redmineClient;
}

private GitHubHelper createGitHubClient() {
GitHubHelper gitHubHelper = null;
if (this.settings.getGitHubApi().isPresent()) {
Expand Down Expand Up @@ -246,6 +270,38 @@ private ParsedIssue createParsedIssue(
labels);
}

private ParsedIssue createParsedIssue(
final RedmineClient redmineClient, final SettingsIssue issuePattern, final String matchedIssue) {
String link = "";
String title = "";
String desc = "";
String issueType = null;
List<String> linkedIssues = null;
List<String> labels = null;
try {
if (redmineClient != null && redmineClient.getIssue(matchedIssue).isPresent()) {
final RedmineIssue redmineIssue = redmineClient.getIssue(matchedIssue).get();
link = redmineIssue.getLink();
title = redmineIssue.getTitle();
issueType = redmineIssue.getIssueType();
desc = redmineIssue.getDescription();
}
} catch (final GitChangelogIntegrationException e) {
LOG.error(matchedIssue, e);
}
return new ParsedIssue( //
REDMINE, //
issuePattern.getName(), //
matchedIssue, //
desc,
link, //
title, //
issueType, //
linkedIssues,
labels);
}


private ParsedIssue createParsedIssue(
final GitHubHelper gitHubHelper,
final SettingsIssue issuePattern,
Expand Down

0 comments on commit afb929c

Please sign in to comment.