Skip to content
New issue

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

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

Already on GitHub? # to your account

Base Ability to Have Custom Fields #153

Closed
wants to merge 11 commits into from
Closed
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ couscous.phar
.idea
classes
*.iml
.vscode
.sdkmanrc

/.nb-gradle/
*.received.txt
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/se/bjurr/gitchangelog/api/GitChangelogApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import se.bjurr.gitchangelog.internal.semantic.SemanticVersioning;
import se.bjurr.gitchangelog.internal.settings.Settings;
import se.bjurr.gitchangelog.internal.settings.SettingsIssue;
import se.bjurr.gitchangelog.internal.settings.SettingsJiraIssueFieldFilter;
import se.bjurr.gitchangelog.internal.util.ResourceLoader;

public class GitChangelogApi {
Expand Down Expand Up @@ -513,6 +514,33 @@ public GitChangelogApi withRedmineUsername(final String string) {
return this;
}

/**
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still don't understand this feature. Are you looking for issues that are not mentioned in the commit message?

When would I want to use this method and what would be the result?

Perhaps these are 2 methods are 2 completely separate features? In that case it might be easier to understand the PR if it is split into 2.

Copy link
Contributor Author

@tthornton3-chwy tthornton3-chwy Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question haha this feature is also not one that I ever need for my team's workflow, but according to the original author of #97:

This feature is needed, if you develop for e.g. multiple clients in one branch and in one Jira project, but each client should only see the commits that belong to him. So you can define a custom field and integrate this into the plugin configuration and filter out all other tickets that are of no interest.

That made kinda sense to me, maybe? I believe you would use search vs issue for when you are wanting an issue returned to potentially be skipped. I tested this w/ Postman by adding essentially an assignee = 'me@email.com' and seeing the ticket either showing up or not.

I am with you that this can be separated into two different PRs, essentially a 'fixed' #97 and then the "additional fields" work I added on top. Since my stuff is what's important to me, this PR will be rebased to only include the additional fields.

* Issue Filter Fields for Jira. When configured, we will use Jira's search API is used instead of
* issues API. These will narrow down the issues returned.<br>
* <br>
* <code>"description", "=", "Testing"</code> becomes<br>
* <code>description='Testing'</code>
*/
public GitChangelogApi withJiraIssueFieldFilter(
final String operator, final String key, final String value) {
this.settings.addJiraIssueFieldFilter(new SettingsJiraIssueFieldFilter(operator, key, value));
return this;
}

/**
* Additional Fields for Jira. When configured, we will return from Jira the results of these
* fields, if they exist.<br>
* <code>"customfield_10000"</code><br>
* <br>
* <code>
* /rest/api/2/issue/JIR-1234?fields=parent,summary,issuetype,labels,description,issuelinks,customfield_10000
* </code>
*/
public GitChangelogApi withJiraIssueAdditionalField(final String field) {
this.settings.addJiraIssueAdditionalField(field);
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
30 changes: 29 additions & 1 deletion src/main/java/se/bjurr/gitchangelog/api/model/Issue.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import se.bjurr.gitchangelog.api.model.interfaces.IAuthors;
import se.bjurr.gitchangelog.api.model.interfaces.ICommits;
import se.bjurr.gitchangelog.internal.settings.SettingsIssueType;
Expand Down Expand Up @@ -55,6 +56,9 @@ public class Issue implements ICommits, IAuthors, Serializable {
private final SettingsIssueType issueType;
private final List<String> linkedIssues;

private final boolean hasAdditionalFields;
private final Map<String, Object> additionalFields;

public Issue(
final List<Commit> commits,
final List<Author> authors,
Expand All @@ -66,7 +70,8 @@ public Issue(
final String link,
final String type,
final List<String> linkedIssues,
final List<String> labels) {
final List<String> labels,
final Map<String, Object> additionalFields) {
checkState(!commits.isEmpty(), "commits");
this.commits = commits;
this.authors = checkNotNull(authors, "authors");
Expand All @@ -86,6 +91,8 @@ public Issue(
this.hasLinkedIssues = linkedIssues != null && !linkedIssues.isEmpty();
this.linkedIssues = linkedIssues;
this.labels = labels;
this.hasAdditionalFields = additionalFields != null && !additionalFields.isEmpty();
this.additionalFields = additionalFields;
}

public SettingsIssueType getIssueType() {
Expand Down Expand Up @@ -186,6 +193,14 @@ public boolean getHasLinkedIssues() {
return this.hasLinkedIssues;
}

public boolean getHasAdditionalFields() {
return this.hasAdditionalFields;
}

public Map<String, Object> getAdditionalFields() {
return this.additionalFields;
}

@Override
public String toString() {
return "Issue: " + this.issue + " Title: " + this.title;
Expand All @@ -205,6 +220,7 @@ public int hashCode() {
result = prime * result + (this.hasLinkedIssues ? 1231 : 1237);
result = prime * result + (this.hasTitle ? 1231 : 1237);
result = prime * result + (this.hasType ? 1231 : 1237);
result = prime * result + (this.hasAdditionalFields ? 1231 : 1237);
result = prime * result + ((this.issue == null) ? 0 : this.issue.hashCode());
result = prime * result + ((this.issueType == null) ? 0 : this.issueType.hashCode());
result = prime * result + ((this.labels == null) ? 0 : this.labels.hashCode());
Expand All @@ -213,6 +229,8 @@ public int hashCode() {
result = prime * result + ((this.name == null) ? 0 : this.name.hashCode());
result = prime * result + ((this.title == null) ? 0 : this.title.hashCode());
result = prime * result + ((this.type == null) ? 0 : this.type.hashCode());
result =
prime * result + ((this.additionalFields == null) ? 0 : this.additionalFields.hashCode());
return result;
}

Expand Down Expand Up @@ -270,6 +288,9 @@ public boolean equals(final Object obj) {
if (this.hasType != other.hasType) {
return false;
}
if (this.hasAdditionalFields != other.hasAdditionalFields) {
return false;
}
if (this.issue == null) {
if (other.issue != null) {
return false;
Expand Down Expand Up @@ -322,6 +343,13 @@ public boolean equals(final Object obj) {
} else if (!this.type.equals(other.type)) {
return false;
}
if (this.additionalFields == null) {
if (other.additionalFields != null) {
return false;
}
} else if (!this.additionalFields.equals(other.additionalFields)) {
return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
package se.bjurr.gitchangelog.internal.integrations.jira;

import static com.jayway.jsonpath.JsonPath.read;
import static org.slf4j.LoggerFactory.getLogger;

import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.slf4j.Logger;
import se.bjurr.gitchangelog.api.exceptions.GitChangelogIntegrationException;
import se.bjurr.gitchangelog.internal.settings.SettingsJiraIssueFieldFilter;

public abstract class JiraClient {
private static final String SEARCH_API = "/search?jql=issue=";
private static final String ISSUE_API = "/issue/";
private static final String ISSUE_API_FIELD_PREFIX = "$.fields.";
private static final String SEARCH_API_FIELD_PREFIX = "$.issues[0].fields.";
private static final String AND = " AND ";
private static final String SINGLE_QUOTE = "'";
private static final String COMMA = ",";
private static final String QUESTION_MARK = "?";
private static final String AMPERSAND = "&";
private static final String EMPTY_STRING = "";
private static final String DEFAULT_FIELDS =
"fields=parent,summary,issuetype,labels,description,issuelinks";
private static final String BASE_PATH = "/rest/api/2";
private static final Logger LOG = getLogger(JiraClient.class);

private final String api;
private List<String> fields = Collections.unmodifiableList(new ArrayList<>());
private List<SettingsJiraIssueFieldFilter> filters =
Collections.unmodifiableList(new ArrayList<>());

public JiraClient(final String api) {
if (api.endsWith("/")) {
Expand All @@ -25,30 +52,106 @@ public String getApi() {
return this.api;
}

protected String getIssuePath() {
return this.hasIssueFieldFilters() ? SEARCH_API : ISSUE_API;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you not always use search API? When can you use it? When can you not use it?

Copy link
Contributor Author

@tthornton3-chwy tthornton3-chwy Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you would use search vs issue for when you are wanting an issue returned to potentially be skipped. I tested this w/ Postman by adding essentially an assignee = 'me@email.com' and seeing the ticket either showing up or not.

}

protected String getEndpoint(final String issue) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method should me unit-tested.

final String endpoint =
this.api
+ "/rest/api/2/issue/"
+ issue
+ "?fields=parent,summary,issuetype,labels,description,issuelinks";
return endpoint;
return this.api
+ BASE_PATH
+ getIssuePath()
+ issue
+ (hasIssueFieldFilters() ? getIssueFieldFiltersQuery() + AMPERSAND : QUESTION_MARK)
+ DEFAULT_FIELDS
+ (hasIssueAdditionalFields() ? COMMA + getIssueAdditionalFieldsQuery() : EMPTY_STRING);
}

private boolean hasIssueFieldFilters() {
return this.filters != null && !filters.isEmpty();
}

private String getIssueFieldFiltersQuery() {
final StringBuffer queryBuffer = new StringBuffer();

for (SettingsJiraIssueFieldFilter filter : filters) {
queryBuffer.append(
AND
+ filter.getKey()
+ filter.getOperator()
+ SINGLE_QUOTE
+ filter.getValue()
+ SINGLE_QUOTE);
}
try {
return URLEncoder.encode(queryBuffer.toString(), "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}

public JiraClient withIssueAdditionalFields(final List<String> fields) {
this.fields = fields;
return this;
}

public JiraClient withIssueFieldFilters(final List<SettingsJiraIssueFieldFilter> filters) {
this.filters = filters;
return this;
}

private boolean hasIssueAdditionalFields() {
return fields != null && !fields.isEmpty();
}

private String getIssueAdditionalFieldsQuery() {
return String.join(COMMA, fields);
}

private String getFieldPrefix() {
return this.hasIssueFieldFilters() ? SEARCH_API_FIELD_PREFIX : ISSUE_API_FIELD_PREFIX;
}

protected JiraIssue toJiraIssue(final String issue, final String json) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this method should me unit-tested.

final String title = read(json, "$.fields.summary");
final String description = read(json, "$.fields.description");
final String type = read(json, "$.fields.issuetype.name");
final String fieldPrefix = getFieldPrefix();

final String title = read(json, fieldPrefix + "summary");
final String description = read(json, fieldPrefix + "description");
final String type = read(json, fieldPrefix + "issuetype.name");
final String link = this.api + "/browse/" + issue;
final List<String> labels = JsonPath.read(json, "$.fields.labels");
final List<String> labels = read(json, fieldPrefix + "labels");
final List<String> linkedIssues = new ArrayList<>();
final List<String> inwardKey = JsonPath.read(json, "$.fields.issuelinks[*].inwardIssue.key");
final List<String> outwardKey = JsonPath.read(json, "$.fields.issuelinks[*].outwardIssue.key");
final List<String> inwardKey = read(json, fieldPrefix + "issuelinks[*].inwardIssue.key");
final List<String> outwardKey = read(json, fieldPrefix + "issuelinks[*].outwardIssue.key");
linkedIssues.addAll(inwardKey);
linkedIssues.addAll(outwardKey);

final JiraIssue jiraIssue =
new JiraIssue(title, description, link, issue, type, linkedIssues, labels);
return jiraIssue;
final Map<String, Object> additionalFields =
fields.stream()
.reduce(
(Map<String, Object>) new HashMap<String, Object>(),
(fields, field) -> getAdditionalField(json, fieldPrefix, fields, field),
(leftSide, rightSide) ->
Stream.of(leftSide, rightSide)
.map(Map::entrySet)
.flatMap(Collection::stream)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)));

return new JiraIssue(
title, description, link, issue, type, linkedIssues, labels, additionalFields);
}

private Map<String, Object> getAdditionalField(
final String json,
final String fieldPrefix,
final Map<String, Object> additionalFields,
final String additionalFieldString) {
try {
additionalFields.put(additionalFieldString, read(json, fieldPrefix + additionalFieldString));
} catch (PathNotFoundException e) {
LOG.warn("Could not find the additional field", e);
}

return additionalFields;
}

public abstract JiraClient withBasicCredentials(String username, String password);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package se.bjurr.gitchangelog.internal.integrations.jira;

import java.util.List;
import java.util.Map;

public class JiraIssue {

Expand All @@ -11,6 +12,7 @@ public class JiraIssue {
private final String description;
private final List<String> linkedIssues;
private final List<String> labels;
private final Map<String, Object> additionalFields;

public JiraIssue(
String title,
Expand All @@ -19,14 +21,16 @@ public JiraIssue(
String issue,
String issueType,
List<String> linkedIssues,
List<String> labels) {
List<String> labels,
Map<String, Object> additionalFields) {
this.title = title;
this.link = link;
this.issue = issue;
this.issueType = issueType;
this.linkedIssues = linkedIssues;
this.labels = labels;
this.description = description;
this.additionalFields = additionalFields;
}

public String getIssue() {
Expand Down Expand Up @@ -54,10 +58,13 @@ public List<String> getLabels() {
}

public String getDescription() {

return description;
}

public Map<String, Object> getAdditionalFields() {
return additionalFields;
}

@Override
public String toString() {
return "JiraIssue [title="
Expand Down
Loading