Skip to content
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
31 changes: 30 additions & 1 deletion src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ interface JiraProjectGroup {
* Mapping of upstream issue types to downstream ones. Please make sure to
* review your project scheme to see which issue types are available.
*/
ValueMapping issueTypes();
IssueTypeValueMapping issueTypes();

/**
* Depending on your downstream JIRA permission schema configuration, Service
Expand Down Expand Up @@ -234,6 +234,35 @@ interface IssueLinkTypeValueMapping extends ValueMapping {
String parentLinkType();
}

interface IssueTypeValueMapping extends ValueMapping {
/**
* @return the name of the custom field that represents the epic link field.
* Apparently there is no clean way to attach issues to an epic, and a
* possible workaround is to use the custom filed (that differs from
* instance to instance) that represents the Epic link key.
* <p>
* A possible alternative could've been <a href=
* "https://developer.atlassian.com/server/jira/platform/rest/v10000/api-group-epic/#api-agile-1-0-epic-epicidorkey-issue-post">Move
* issues to a specific epic</a> but it might not be available on all
* instance types.
* <p>
* If value is not provided in the configuration, then tickets will not
* be linked to epics during the sync.
*/
Optional<String> epicLinkKeyCustomFieldName();

/**
* @return The name of a custom field that represents the "epic name" i.e.
* epic-short-label in the upstream (source) Jira instance.
*/
Optional<String> epicLinkSourceLabelCustomFieldName();
/**
* @return The name of a custom field that represents the "epic name" i.e.
* epic-short-label in the downstream (destination) Jira instance.
*/
Optional<String> epicLinkDestinationLabelCustomFieldName();
}

interface UserValueMapping extends ValueMapping {
/**
* @return the name of the property to apply the assignee value to. With Jira
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class JiraWebHookListenerResource {
@Consumes(MediaType.APPLICATION_JSON)
public String somethingHappened(@RestPath @NotNull /* @ConfiguredProject */ String project,
JiraWebHookEvent event) {
Log.infof("Received a notification about %s project: %s", project, event);
Log.infof("Received a notification about %s project: %.200s...", project, event);
jiraService.acknowledge(project, event);
return "ack";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Objects;
import java.util.Optional;

import org.hibernate.infra.replicate.jira.JiraConfig;
import org.hibernate.infra.replicate.jira.service.jira.HandlerProjectContext;
import org.hibernate.infra.replicate.jira.service.jira.client.JiraRestException;
import org.hibernate.infra.replicate.jira.service.jira.model.rest.JiraFields;
Expand Down Expand Up @@ -128,6 +129,24 @@ protected JiraIssue issueToCreate(JiraIssue sourceIssue) {
destinationIssue.fields.assignee = JiraUser.unassigned(context.projectGroup().users().mappedPropertyName());
}

if (!isSubTaskIssue(sourceIssue.fields.issuetype) && sourceIssue.fields.parent != null) {
if (isEpicIssue(sourceIssue.fields.parent.fields.issuetype)) {
JiraConfig.IssueTypeValueMapping issueTypes = context.projectGroup().issueTypes();
issueTypes.epicLinkKeyCustomFieldName().ifPresent(epicLinkCustomFieldName -> destinationIssue.fields
.properties().put(epicLinkCustomFieldName, toDestinationKey(sourceIssue.fields.parent.key)));
}
}
if (isEpicIssue(sourceIssue.fields.issuetype)) {
// Try to set an epic label ... obviously it is a custom field >_< ...
JiraConfig.IssueTypeValueMapping issueTypes = context.projectGroup().issueTypes();
Object sourceEpicLabel = issueTypes.epicLinkSourceLabelCustomFieldName()
.map(sourceIssue.fields.properties()::get).orElse(sourceIssue.fields.summary);

issueTypes.epicLinkDestinationLabelCustomFieldName()
.ifPresent(epicLinkDestinationLabelCustomFieldName -> destinationIssue.fields.properties()
.put(epicLinkDestinationLabelCustomFieldName, sourceEpicLabel));
}

return destinationIssue;
}

Expand All @@ -140,7 +159,10 @@ private Optional<JiraTransition> prepareTransition(JiraIssue sourceIssue) {
}

protected Optional<JiraIssueLink> prepareParentLink(String destinationKey, JiraIssue sourceIssue) {
if (sourceIssue.fields.parent != null) {
// we only add a link for sub-tasks (the ones that have a corresponding types).
// Issues assigned to an epic, can also have a parent.
// but for those we won't add an extra link:
if (sourceIssue.fields.parent != null && isSubTaskIssue(sourceIssue.fields.issuetype)) {
String parent = toDestinationKey(sourceIssue.fields.parent.key);
// we don't really need it, but as usual we are making sure that the issue is
// available downstream:
Expand All @@ -164,6 +186,20 @@ protected Optional<JiraIssueLink> prepareParentLink(String destinationKey, JiraI
}
}

private boolean isSubTaskIssue(JiraSimpleObject issueType) {
if (issueType == null) {
return false;
}
return Boolean.parseBoolean(Objects.toString(issueType.properties().get("subtask"), null));
}

private boolean isEpicIssue(JiraSimpleObject issueType) {
if (issueType == null) {
return false;
}
return "epic".equalsIgnoreCase(issueType.name);
}

private String prepareDescriptionQuote(JiraIssue issue) {
URI issueUri = createJiraIssueUri(issue);

Expand Down
Loading