Skip to content

Commit ff0090b

Browse files
committed
fix: importer ignores attributes without values
1 parent d723cd3 commit ff0090b

File tree

3 files changed

+71
-61
lines changed

3 files changed

+71
-61
lines changed

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ category: Administration
2020
* feat: ensure compatibility with Jira 11.2.0
2121
* refactor: extract URL validation into util class
2222
* feat: ensure compatibility with Jira 11.3.0
23+
* fix: importer ignores attributes without values
2324

2425
### [25.10.0] - 2025-10-03
2526

src/main/java/de/codescape/jira/plugins/multiplesubtasks/action/SubtaskTemplateImportAction.java

Lines changed: 63 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public class SubtaskTemplateImportAction extends JiraWebActionSupport {
4444

4545
private static final String QUICK_SUBTASKS_PROJECT_TEMPLATES = "com.hascode.plugin.jira:subtask-templates";
4646
private static final String QUICK_SUBTASKS_USER_TEMPLATES_PREFIX = "subtasks-user-";
47-
private static final Pattern QUICK_SUBTASKS_TASK_PATTERN = Pattern.compile("^\\s*- *([^/]+)/?(?:\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]+)\")?\\s*/?\\s*|[^\\r\\n]*)$");
47+
private static final Pattern QUICK_SUBTASKS_TASK_PATTERN = Pattern.compile("^\\s*- *([^/]+)/?(?:\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*(?: ([^:]+):\"([^\"]*)\")?\\s*/?\\s*|[^\\r\\n]*)$");
4848
private static final String NEWLINE = "\n";
4949

5050
private final UserSearchService userSearchService;
@@ -285,66 +285,68 @@ String transformQuickSubtasksTemplate(String templateString) {
285285
key = matcher.group(i);
286286
} else {
287287
value = matcher.group(i);
288-
switch (key.toLowerCase()) {
289-
// same keyword and value format
290-
case "priority":
291-
case "description":
292-
case "estimate":
293-
case "assignee":
294-
case "reporter":
295-
output.append(" ").append(key.toLowerCase()).append(": ").append(value).append(NEWLINE);
296-
break;
297-
// different keyword same value
298-
case "issuetype":
299-
output.append(" ").append(Subtask.Attributes.ISSUE_TYPE).append(": ").append(value).append(NEWLINE);
300-
break;
301-
case "duedate":
302-
output.append(" ").append(Subtask.Attributes.DUE_DATE).append(": ").append(value).append(NEWLINE);
303-
break;
304-
case "fixversion":
305-
output.append(" ").append(Subtask.Attributes.FIX_VERSION).append(": ").append(value).append(NEWLINE);
306-
break;
307-
case "affectedversion":
308-
output.append(" ").append(Subtask.Attributes.AFFECTED_VERSION).append(": ").append(value).append(NEWLINE);
309-
break;
310-
// component (split values)
311-
case "component":
312-
String[] components = value.split(", *");
313-
Arrays.stream(components).forEach(component ->
314-
output.append(" ").append(Subtask.Attributes.COMPONENT).append(": ").append(component).append(NEWLINE)
315-
);
316-
break;
317-
// labels (split values)
318-
case "labels":
319-
String[] labels = value.split(", *");
320-
Arrays.stream(labels).forEach(label ->
321-
output.append(" ").append(Subtask.Attributes.LABEL).append(": ").append(label).append(NEWLINE)
322-
);
323-
break;
324-
// watcher (split values)
325-
case "watcher":
326-
String[] watchers = value.split(", *");
327-
Arrays.stream(watchers).forEach(watcher ->
328-
output.append(" ").append(Subtask.Attributes.WATCHER).append(": ").append(watcher).append(NEWLINE)
329-
);
330-
break;
331-
// custom fields
332-
case "cfield":
333-
String[] tokens = value.split(":", 2);
334-
if (tokens.length == 2) {
335-
String customFieldName = tokens[0]
336-
.replaceAll("\\(", "\\\\(")
337-
.replaceAll("\\)", "\\\\)")
338-
.replaceAll(":", "\\:")
339-
.trim();
340-
output.append(" ").append("customfield(").append(customFieldName).append("): ").append(tokens[1]).append(NEWLINE);
341-
} else {
342-
log.error("Invalid custom field attributes: " + value);
343-
}
344-
break;
345-
// not supported (will be ignored)
346-
default:
347-
log.error("Ignoring unknown attribute '" + key + "' with value: '" + value + "'");
288+
if (value != null && !value.isEmpty()) {
289+
switch (key.toLowerCase()) {
290+
// same keyword and value format
291+
case "priority":
292+
case "description":
293+
case "estimate":
294+
case "assignee":
295+
case "reporter":
296+
output.append(" ").append(key.toLowerCase()).append(": ").append(value).append(NEWLINE);
297+
break;
298+
// different keyword same value
299+
case "issuetype":
300+
output.append(" ").append(Subtask.Attributes.ISSUE_TYPE).append(": ").append(value).append(NEWLINE);
301+
break;
302+
case "duedate":
303+
output.append(" ").append(Subtask.Attributes.DUE_DATE).append(": ").append(value).append(NEWLINE);
304+
break;
305+
case "fixversion":
306+
output.append(" ").append(Subtask.Attributes.FIX_VERSION).append(": ").append(value).append(NEWLINE);
307+
break;
308+
case "affectedversion":
309+
output.append(" ").append(Subtask.Attributes.AFFECTED_VERSION).append(": ").append(value).append(NEWLINE);
310+
break;
311+
// component (split values)
312+
case "component":
313+
String[] components = value.split(", *");
314+
Arrays.stream(components).forEach(component ->
315+
output.append(" ").append(Subtask.Attributes.COMPONENT).append(": ").append(component).append(NEWLINE)
316+
);
317+
break;
318+
// labels (split values)
319+
case "labels":
320+
String[] labels = value.split(", *");
321+
Arrays.stream(labels).forEach(label ->
322+
output.append(" ").append(Subtask.Attributes.LABEL).append(": ").append(label).append(NEWLINE)
323+
);
324+
break;
325+
// watcher (split values)
326+
case "watcher":
327+
String[] watchers = value.split(", *");
328+
Arrays.stream(watchers).forEach(watcher ->
329+
output.append(" ").append(Subtask.Attributes.WATCHER).append(": ").append(watcher).append(NEWLINE)
330+
);
331+
break;
332+
// custom fields
333+
case "cfield":
334+
String[] tokens = value.split(":", 2);
335+
if (tokens.length == 2) {
336+
String customFieldName = tokens[0]
337+
.replaceAll("\\(", "\\\\(")
338+
.replaceAll("\\)", "\\\\)")
339+
.replaceAll(":", "\\:")
340+
.trim();
341+
output.append(" ").append("customfield(").append(customFieldName).append("): ").append(tokens[1]).append(NEWLINE);
342+
} else {
343+
log.error("Invalid custom field attributes: " + value);
344+
}
345+
break;
346+
// not supported (will be ignored)
347+
default:
348+
log.error("Ignoring unknown attribute '" + key + "' with value: '" + value + "'");
349+
}
348350
}
349351
}
350352
} else {

src/test/java/de/codescape/jira/plugins/multiplesubtasks/action/SubtaskTemplateImportActionTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,13 @@ public void shouldAcceptUncommonLetters() {
275275
assertThat(templates.get(0).getTemplate(), containsString("Serve a drink in the Café"));
276276
}
277277

278+
/* fix: do not fail on empty attribute values */
279+
@Test
280+
public void shouldIgnoreAttributesWithoutValue() {
281+
String input = "- a subtask with / component:\"backend\" issueType:\"test\" assignee:\"\" estimate:\"\"";
282+
assertTransformation(input,"- a subtask with\n component: backend\n issueType: test\n");
283+
}
284+
278285
/* helper methods */
279286

280287
private void assertTransformation(String input, String output) {

0 commit comments

Comments
 (0)