From 5799e9d21ff1ccd2079c2b5fd0fc2e29f4670f79 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:07:49 -0600 Subject: [PATCH 1/3] Jira Epic Mapping: Add flexibility to epic issue type --- .../0204_jira_project_epic_issue_type_name.py | 18 ++++++++++++++++++ dojo/forms.py | 5 ++++- dojo/jira_link/helper.py | 2 +- dojo/models.py | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 dojo/db_migrations/0204_jira_project_epic_issue_type_name.py diff --git a/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py b/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py new file mode 100644 index 00000000000..ce9b5f4b10f --- /dev/null +++ b/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-03-01 20:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0203_alter_finding_options_finding_epss_percentile_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='jira_project', + name='epic_issue_type_name', + field=models.CharField(default='Epic', help_text='The name of the of structure that represents an Epic', max_length=64), + ), + ] diff --git a/dojo/forms.py b/dojo/forms.py index 10ea312ea91..70607387696 100755 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -2709,7 +2709,7 @@ class JIRAProjectForm(forms.ModelForm): class Meta: model = JIRA_Project exclude = ['product', 'engagement'] - fields = ['inherit_from_product', 'jira_instance', 'project_key', 'issue_template_dir', 'component', 'custom_fields', 'jira_labels', 'default_assignee', 'add_vulnerability_id_to_jira_label', 'push_all_issues', 'enable_engagement_epic_mapping', 'push_notes', 'product_jira_sla_notification', 'risk_acceptance_expiration_notification'] + fields = ['inherit_from_product', 'jira_instance', 'project_key', 'issue_template_dir', 'epic_issue_type_name', 'component', 'custom_fields', 'jira_labels', 'default_assignee', 'add_vulnerability_id_to_jira_label', 'push_all_issues', 'enable_engagement_epic_mapping', 'push_notes', 'product_jira_sla_notification', 'risk_acceptance_expiration_notification'] def __init__(self, *args, **kwargs): from dojo.jira_link import helper as jira_helper @@ -2742,6 +2742,7 @@ def __init__(self, *args, **kwargs): self.fields['jira_instance'].disabled = False self.fields['project_key'].disabled = False self.fields['issue_template_dir'].disabled = False + self.fields['epic_issue_type_name'].disabled = False self.fields['component'].disabled = False self.fields['custom_fields'].disabled = False self.fields['default_assignee'].disabled = False @@ -2765,6 +2766,7 @@ def __init__(self, *args, **kwargs): self.initial['jira_instance'] = jira_project_product.jira_instance.id if jira_project_product.jira_instance else None self.initial['project_key'] = jira_project_product.project_key self.initial['issue_template_dir'] = jira_project_product.issue_template_dir + self.initial['epic_issue_type_name'] = jira_project_product.epic_issue_type_name self.initial['component'] = jira_project_product.component self.initial['custom_fields'] = jira_project_product.custom_fields self.initial['default_assignee'] = jira_project_product.default_assignee @@ -2779,6 +2781,7 @@ def __init__(self, *args, **kwargs): self.fields['jira_instance'].disabled = True self.fields['project_key'].disabled = True self.fields['issue_template_dir'].disabled = True + self.fields['epic_issue_type_name'].disabled = True self.fields['component'].disabled = True self.fields['custom_fields'].disabled = True self.fields['default_assignee'].disabled = True diff --git a/dojo/jira_link/helper.py b/dojo/jira_link/helper.py index 5318aa0e3ed..4f7360fc465 100644 --- a/dojo/jira_link/helper.py +++ b/dojo/jira_link/helper.py @@ -1223,7 +1223,7 @@ def add_epic(engagement, **kwargs): 'summary': epic_name, 'description': epic_name, 'issuetype': { - 'name': 'Epic' + 'name': getattr(jira_project, "epic_issue_type_name", "Epic"), }, get_epic_name_field_name(jira_instance): epic_name, } diff --git a/dojo/models.py b/dojo/models.py index 98922853f47..2475fef52b2 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -3890,6 +3890,7 @@ class JIRA_Project(models.Model): help_text=_("Automatically maintain parity with JIRA. Always create and update JIRA tickets for findings in this Product.")) enable_engagement_epic_mapping = models.BooleanField(default=False, blank=True) + epic_issue_type_name = models.CharField(max_length=64, default="Epic", help_text=_("The name of the of structure that represents an Epic")) push_notes = models.BooleanField(default=False, blank=True) product_jira_sla_notification = models.BooleanField(default=False, blank=True, verbose_name=_("Send SLA notifications as comment?")) risk_acceptance_expiration_notification = models.BooleanField(default=False, blank=True, verbose_name=_("Send Risk Acceptance expiration notifications as comment?")) From 35646607043da575b429100ac2824fdbbe57aa72 Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Tue, 5 Mar 2024 13:59:56 -0600 Subject: [PATCH 2/3] Update unit tests --- .../0204_jira_project_epic_issue_type_name.py | 2 +- dojo/forms.py | 10 ++++++---- dojo/models.py | 2 +- unittests/dojo_test_case.py | 10 ++++++++-- unittests/test_jira_config_engagement.py | 8 ++++++++ 5 files changed, 24 insertions(+), 8 deletions(-) diff --git a/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py b/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py index ce9b5f4b10f..88b5f922a03 100644 --- a/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py +++ b/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py @@ -13,6 +13,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='jira_project', name='epic_issue_type_name', - field=models.CharField(default='Epic', help_text='The name of the of structure that represents an Epic', max_length=64), + field=models.CharField(default='Epic', blank=True, help_text='The name of the of structure that represents an Epic', max_length=64), ), ] diff --git a/dojo/forms.py b/dojo/forms.py index 70607387696..4047ac7aeb3 100755 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -2801,6 +2801,7 @@ def __init__(self, *args, **kwargs): if self.instance.id: self.fields['jira_instance'].required = True self.fields['project_key'].required = True + self.fields['epic_issue_type_name'].required = True def clean(self): logger.debug('validating jira project form') @@ -2810,17 +2811,18 @@ def clean(self): if not self.cleaned_data.get('inherit_from_product', False): jira_instance = self.cleaned_data.get('jira_instance') project_key = self.cleaned_data.get('project_key') + epic_issue_type_name = self.cleaned_data.get('epic_issue_type_name') - if project_key and jira_instance: + if project_key and jira_instance and epic_issue_type_name: return cleaned_data - if not project_key and not jira_instance: + if not project_key and not jira_instance and not epic_issue_type_name: return cleaned_data if self.target == 'engagement': - raise ValidationError('JIRA Project needs a JIRA Instance and JIRA Project Key, or choose to inherit settings from product') + raise ValidationError('JIRA Project needs a JIRA Instance, JIRA Project Key, and Epic issue type name, or choose to inherit settings from product') else: - raise ValidationError('JIRA Project needs a JIRA Instance and JIRA Project Key, leave empty to have no JIRA integration setup') + raise ValidationError('JIRA Project needs a JIRA Instance, JIRA Project Key, and Epic issue type name, leave empty to have no JIRA integration setup') class GITHUBFindingForm(forms.Form): diff --git a/dojo/models.py b/dojo/models.py index 2475fef52b2..36918c88dec 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -3890,7 +3890,7 @@ class JIRA_Project(models.Model): help_text=_("Automatically maintain parity with JIRA. Always create and update JIRA tickets for findings in this Product.")) enable_engagement_epic_mapping = models.BooleanField(default=False, blank=True) - epic_issue_type_name = models.CharField(max_length=64, default="Epic", help_text=_("The name of the of structure that represents an Epic")) + epic_issue_type_name = models.CharField(max_length=64, blank=True, default="Epic", help_text=_("The name of the of structure that represents an Epic")) push_notes = models.BooleanField(default=False, blank=True) product_jira_sla_notification = models.BooleanField(default=False, blank=True, verbose_name=_("Send SLA notifications as comment?")) risk_acceptance_expiration_notification = models.BooleanField(default=False, blank=True, verbose_name=_("Send Risk Acceptance expiration notifications as comment?")) diff --git a/unittests/dojo_test_case.py b/unittests/dojo_test_case.py index f6f08679b42..48647f69524 100644 --- a/unittests/dojo_test_case.py +++ b/unittests/dojo_test_case.py @@ -158,6 +158,7 @@ def get_new_product_with_jira_project_data(self): 'jira-project-form-project_key': 'IFFFNEW', 'jira-project-form-jira_instance': 2, 'jira-project-form-enable_engagement_epic_mapping': 'on', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-push_notes': 'on', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-custom_fields': 'null', @@ -170,7 +171,9 @@ def get_new_product_without_jira_project_data(self): 'name': 'new product', 'description': 'new description', 'prod_type': 1, - 'sla_configuration': 1 + 'sla_configuration': 1, + # A value is set by default by the model, so we need to add it here as well + 'jira-project-form-epic_issue_type_name': 'Epic', # 'project_key': 'IFFF', # 'jira_instance': 2, # 'enable_engagement_epic_mapping': 'on', @@ -186,6 +189,7 @@ def get_product_with_jira_project_data(self, product): 'jira-project-form-project_key': 'IFFF', 'jira-project-form-jira_instance': 2, 'jira-project-form-enable_engagement_epic_mapping': 'on', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-push_notes': 'on', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-custom_fields': 'null', @@ -201,6 +205,7 @@ def get_product_with_jira_project_data2(self, product): 'jira-project-form-project_key': 'IFFF2', 'jira-project-form-jira_instance': 2, 'jira-project-form-enable_engagement_epic_mapping': 'on', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-push_notes': 'on', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-custom_fields': 'null', @@ -214,7 +219,8 @@ def get_product_with_empty_jira_project_data(self, product): 'description': product.description, 'prod_type': product.prod_type.id, 'sla_configuration': 1, - + # A value is set by default by the model, so we need to add it here as well + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-custom_fields': 'null', # 'project_key': 'IFFF', # 'jira_instance': 2, diff --git a/unittests/test_jira_config_engagement.py b/unittests/test_jira_config_engagement.py index f6922c19166..9506a264f60 100644 --- a/unittests/test_jira_config_engagement.py +++ b/unittests/test_jira_config_engagement.py @@ -24,6 +24,7 @@ def get_new_engagement_with_jira_project_data(self): # 'jira-project-form-inherit_from_product': 'on', # absence = False in html forms 'jira-project-form-jira_instance': 2, 'jira-project-form-project_key': 'IUNSEC', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-custom_fields': 'null', } @@ -40,6 +41,7 @@ def get_new_engagement_with_jira_project_data_and_epic_mapping(self): # 'jira-project-form-inherit_from_product': 'on', # absence = False in html forms 'jira-project-form-jira_instance': 2, 'jira-project-form-project_key': 'IUNSEC', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-enable_engagement_epic_mapping': 'on', 'jira-epic-form-push_to_jira': 'on', @@ -56,6 +58,8 @@ def get_new_engagement_without_jira_project_data(self): 'target_end': '2070-12-04', 'status': 'Not Started', 'jira-project-form-inherit_from_product': 'on', + # A value is set by default by the model, so we need to add it here as well + 'jira-project-form-epic_issue_type_name': 'Epic', # 'project_key': 'IFFF', # 'jira_instance': 2, # 'enable_engagement_epic_mapping': 'on', @@ -75,6 +79,7 @@ def get_engagement_with_jira_project_data(self, engagement): # 'jira-project-form-inherit_from_product': 'on', # absence = False in html forms 'jira-project-form-jira_instance': 2, 'jira-project-form-project_key': 'ISEC', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-custom_fields': 'null', } @@ -91,6 +96,7 @@ def get_engagement_with_jira_project_data2(self, engagement): # 'jira-project-form-inherit_from_product': 'on', # absence = False in html forms 'jira-project-form-jira_instance': 2, 'jira-project-form-project_key': 'ISEC2', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-custom_fields': 'null', } @@ -105,6 +111,8 @@ def get_engagement_with_empty_jira_project_data(self, engagement): 'target_end': '2070-12-04', 'status': 'Not Started', 'jira-project-form-inherit_from_product': 'on', + # A value is set by default by the model, so we need to add it here as well + 'jira-project-form-epic_issue_type_name': 'Epic', # 'project_key': 'IFFF', # 'jira_instance': 2, # 'enable_engagement_epic_mapping': 'on', From 986b4f5d2a9f7c0cc1a08670d2b48d66772ac3ba Mon Sep 17 00:00:00 2001 From: Cody Maffucci <46459665+Maffooch@users.noreply.github.com> Date: Tue, 5 Mar 2024 14:27:38 -0600 Subject: [PATCH 3/3] Missed a test --- unittests/test_jira_config_engagement_epic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/unittests/test_jira_config_engagement_epic.py b/unittests/test_jira_config_engagement_epic.py index 457f392f273..51989a3829e 100644 --- a/unittests/test_jira_config_engagement_epic.py +++ b/unittests/test_jira_config_engagement_epic.py @@ -55,6 +55,7 @@ def get_new_engagement_with_jira_project_data_and_epic_mapping(self): 'status': 'Not Started', 'jira-project-form-jira_instance': 2, 'jira-project-form-project_key': 'NTEST', + 'jira-project-form-epic_issue_type_name': 'Epic', 'jira-project-form-product_jira_sla_notification': 'on', 'jira-project-form-enable_engagement_epic_mapping': 'on', 'jira-epic-form-push_to_jira': 'on',