Skip to content

Commit f14a6d1

Browse files
committed
Handling of Epic labels and links
1 parent 4e46240 commit f14a6d1

File tree

2 files changed

+67
-2
lines changed

2 files changed

+67
-2
lines changed

src/main/java/org/hibernate/infra/replicate/jira/JiraConfig.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ interface JiraProjectGroup {
6161
* Mapping of upstream issue types to downstream ones. Please make sure to
6262
* review your project scheme to see which issue types are available.
6363
*/
64-
ValueMapping issueTypes();
64+
IssueTypeValueMapping issueTypes();
6565

6666
/**
6767
* Depending on your downstream JIRA permission schema configuration, Service
@@ -234,6 +234,35 @@ interface IssueLinkTypeValueMapping extends ValueMapping {
234234
String parentLinkType();
235235
}
236236

237+
interface IssueTypeValueMapping extends ValueMapping {
238+
/**
239+
* @return the name of the custom field that represents the epic link field.
240+
* Apparently there is no clean way to attach issues to an epic, and a
241+
* possible workaround is to use the custom filed (that differs from
242+
* instance to instance) that represents the Epic link key.
243+
* <p>
244+
* A possible alternative could've been <a href=
245+
* "https://developer.atlassian.com/server/jira/platform/rest/v10000/api-group-epic/#api-agile-1-0-epic-epicidorkey-issue-post">Move
246+
* issues to a specific epic</a> but it might not be available on all
247+
* instance types.
248+
* <p>
249+
* If value is not provided in the configuration, then tickets will not
250+
* be linked to epics during the sync.
251+
*/
252+
Optional<String> epicLinkKeyCustomFieldName();
253+
254+
/**
255+
* @return The name of a custom field that represents the "epic name" i.e.
256+
* epic-short-label in the upstream (source) Jira instance.
257+
*/
258+
Optional<String> epicLinkSourceLabelCustomFieldName();
259+
/**
260+
* @return The name of a custom field that represents the "epic name" i.e.
261+
* epic-short-label in the downstream (destination) Jira instance.
262+
*/
263+
Optional<String> epicLinkDestinationLabelCustomFieldName();
264+
}
265+
237266
interface UserValueMapping extends ValueMapping {
238267
/**
239268
* @return the name of the property to apply the assignee value to. With Jira

src/main/java/org/hibernate/infra/replicate/jira/service/jira/handler/JiraIssueAbstractEventHandler.java

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import java.util.Objects;
77
import java.util.Optional;
88

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

132+
if (!isSubTaskIssue(sourceIssue.fields.issuetype) && sourceIssue.fields.parent != null) {
133+
if (isEpicIssue(sourceIssue.fields.parent.fields.issuetype)) {
134+
JiraConfig.IssueTypeValueMapping issueTypes = context.projectGroup().issueTypes();
135+
issueTypes.epicLinkKeyCustomFieldName().ifPresent(epicLinkCustomFieldName -> destinationIssue.fields
136+
.properties().put(epicLinkCustomFieldName, toDestinationKey(sourceIssue.fields.parent.key)));
137+
}
138+
}
139+
if (isEpicIssue(sourceIssue.fields.issuetype)) {
140+
// Try to set an epic label ... obviously it is a custom field >_< ...
141+
JiraConfig.IssueTypeValueMapping issueTypes = context.projectGroup().issueTypes();
142+
Object sourceEpicLabel = issueTypes.epicLinkSourceLabelCustomFieldName()
143+
.map(sourceIssue.fields.properties()::get).orElse(sourceIssue.fields.summary);
144+
145+
issueTypes.epicLinkDestinationLabelCustomFieldName()
146+
.ifPresent(epicLinkDestinationLabelCustomFieldName -> destinationIssue.fields.properties()
147+
.put(epicLinkDestinationLabelCustomFieldName, sourceEpicLabel));
148+
}
149+
131150
return destinationIssue;
132151
}
133152

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

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

189+
private boolean isSubTaskIssue(JiraSimpleObject issueType) {
190+
if (issueType == null) {
191+
return false;
192+
}
193+
return Boolean.parseBoolean(Objects.toString(issueType.properties().get("subtask"), null));
194+
}
195+
196+
private boolean isEpicIssue(JiraSimpleObject issueType) {
197+
if (issueType == null) {
198+
return false;
199+
}
200+
return "epic".equalsIgnoreCase(issueType.name);
201+
}
202+
167203
private String prepareDescriptionQuote(JiraIssue issue) {
168204
URI issueUri = createJiraIssueUri(issue);
169205

0 commit comments

Comments
 (0)