Skip to content
Open
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
2 changes: 1 addition & 1 deletion docs/content/issue_tracking/jira/OS__jira_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ Here is an example of a **jira\_full** Issue:

#### Component

If you manage your Jira Space using Components, you can assign the appropriate Component for DefectDojo here.
If you manage your Jira Space using Components, you can assign the appropriate Component for DefectDojo here. To assign more than one Component, enter a comma-separated list (for example, `Security, DevSecOps`); each value is sent to Jira as a separate component.

**Custom fields**

Expand Down
2 changes: 1 addition & 1 deletion docs/content/issue_tracking/jira/PRO__jira_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ Here is an example of a **jira\_full** Issue:

#### Component

If you manage your Jira Space using Components, you can assign the appropriate Component for DefectDojo here.
If you manage your Jira Space using Components, you can assign the appropriate Component for DefectDojo here. To assign more than one Component, enter a comma-separated list (for example, `Security, DevSecOps`); each value is sent to Jira as a separate component.

#### Custom fields

Expand Down
5 changes: 5 additions & 0 deletions dojo/jira/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ def __init__(self, *args, **kwargs):
self.engagement = kwargs.pop("engagement", None)
super().__init__(*args, **kwargs)

self.fields["component"].help_text = (
"Comma-separate multiple components to assign more than one to the JIRA issue, "
"e.g. 'Security, DevSecOps'."
)

logger.debug("self.target: %s, self.product: %s, self.instance: %s", self.target, self.product, self.instance)
logger.debug("data: %s", self.data)
if self.target == "engagement":
Expand Down
7 changes: 6 additions & 1 deletion dojo/jira/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,7 +867,12 @@ def prepare_jira_issue_fields(
}

if component_name:
fields["components"] = [{"name": component_name}]
# The component field holds a comma-separated list of component names, so split it
# into the list of components Jira expects ([{"name": "A"}, {"name": "B"}]). A single
# value without commas yields a single component. (SC-13173)
components = [{"name": name.strip()} for name in component_name.split(",") if name.strip()]
if components:
fields["components"] = components

if custom_fields:
fields.update(custom_fields)
Expand Down
46 changes: 46 additions & 0 deletions unittests/test_jira_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,49 @@ def test_issue_from_jira_is_active_with_unknown_status(self):
def test_issue_from_jira_is_active_defaults_to_active_on_missing_attribute(self):
"""AttributeError anywhere in the fields.status.statusCategory.key chain defaults to active."""
self.assertTrue(jira_helper.issue_from_jira_is_active(Mock(spec=[])))


class JIRAComponentFieldTest(TestCase):

"""
SC-13173: the JIRA project `component` field holds a comma-separated list of
component names. prepare_jira_issue_fields must split it into multiple Jira
components so Jira receives [{"name": "A"}, {"name": "B"}] instead of a single
component named "A,B".
"""

def _fields(self, component_name):
return jira_helper.prepare_jira_issue_fields(
project_key="PROJ",
issuetype_name="Bug",
summary="summary",
description="description",
component_name=component_name,
)

def test_single_component(self):
fields = self._fields("Security")
self.assertEqual([{"name": "Security"}], fields["components"])

def test_multiple_components_split_on_comma(self):
fields = self._fields("Security,DevSecOps")
self.assertEqual([{"name": "Security"}, {"name": "DevSecOps"}], fields["components"])

def test_multiple_components_whitespace_trimmed(self):
fields = self._fields("Security, DevSecOps , Platform")
self.assertEqual(
[{"name": "Security"}, {"name": "DevSecOps"}, {"name": "Platform"}],
fields["components"],
)

def test_empty_entries_dropped(self):
fields = self._fields("Security,,DevSecOps,")
self.assertEqual([{"name": "Security"}, {"name": "DevSecOps"}], fields["components"])

def test_no_component_omits_field(self):
fields = self._fields("")
self.assertNotIn("components", fields)

def test_only_separators_omits_field(self):
fields = self._fields(" , , ")
self.assertNotIn("components", fields)
Loading