diff --git a/dojo/engagement/urls.py b/dojo/engagement/urls.py index c70bb56a95e..0f33c3aa697 100644 --- a/dojo/engagement/urls.py +++ b/dojo/engagement/urls.py @@ -30,6 +30,8 @@ name="close_engagement"), re_path(r"^engagement/(?P\d+)/reopen$", views.reopen_eng, name="reopen_engagement"), + re_path(r"^engagement/(?P\d+)/jira/unlink$", views.unlink_jira, + name="engagement_unlink_jira"), re_path(r"^engagement/(?P\d+)/complete_checklist$", views.complete_checklist, name="complete_checklist"), re_path(r"^engagement/(?P\d+)/risk_acceptance/add$", diff --git a/dojo/engagement/views.py b/dojo/engagement/views.py index 7ae3e758ead..3ff397adc77 100644 --- a/dojo/engagement/views.py +++ b/dojo/engagement/views.py @@ -19,13 +19,14 @@ from django.db.models import OuterRef, Q, Value from django.db.models.functions import Coalesce from django.db.models.query import Prefetch, QuerySet -from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, QueryDict, StreamingHttpResponse +from django.http import HttpRequest, HttpResponse, HttpResponseRedirect, JsonResponse, QueryDict, StreamingHttpResponse from django.shortcuts import get_object_or_404, render from django.urls import Resolver404, reverse from django.utils import timezone from django.utils.translation import gettext as _ from django.views import View from django.views.decorators.cache import cache_page +from django.views.decorators.http import require_POST from django.views.decorators.vary import vary_on_cookie from openpyxl import Workbook from openpyxl.styles import Font @@ -1134,6 +1135,40 @@ def close_eng(request, eid): return HttpResponseRedirect(reverse("view_engagements", args=(eng.product.id, ))) +@user_is_authorized(Engagement, Permissions.Engagement_Edit, "eid") +@require_POST +def unlink_jira(request, eid): + eng = get_object_or_404(Engagement, id=eid) + logger.info("trying to unlink a linked jira epic from engagement %d:%s", eng.id, eng.name) + if eng.has_jira_issue: + try: + jira_helper.unlink_jira(request, eng) + messages.add_message( + request, + messages.SUCCESS, + "Link to JIRA epic successfully deleted", + extra_tags="alert-success", + ) + return JsonResponse({"result": "OK"}) + except Exception: + logger.exception("Link to JIRA epic could not be deleted") + messages.add_message( + request, + messages.ERROR, + "Link to JIRA epic could not be deleted, see alerts for details", + extra_tags="alert-danger", + ) + return HttpResponse(status=500) + else: + messages.add_message( + request, + messages.ERROR, + "Link to JIRA epic not found", + extra_tags="alert-danger", + ) + return HttpResponse(status=400) + + @user_is_authorized(Engagement, Permissions.Engagement_Edit, "eid") def reopen_eng(request, eid): eng = Engagement.objects.get(id=eid) diff --git a/dojo/templates/dojo/view_eng.html b/dojo/templates/dojo/view_eng.html index 728b8867f7d..ab09dadb7c5 100644 --- a/dojo/templates/dojo/view_eng.html +++ b/dojo/templates/dojo/view_eng.html @@ -826,13 +826,18 @@

{% if jissue and jira_project %} - - Jira - {{ eng | jira_key }} + + Jira + + {{ eng | jira_key }} (epic) - - - + + {% if eng|has_object_permission:"Engagement_Edit" %} +   + + {% endif %} + + {% elif jira_project %} JIRA @@ -1088,6 +1093,28 @@

var host = slashes.concat(window.location.host); modal.find('p#questionnaireURL').text('Questionnaire URL: ' + host + path) }) + + function jira_action(elem, url) { + $(elem).removeClass().addClass('fa-solid fa-spin fa-spinner') + + $.ajax({ + type: "post", + dataType:'json', + data: '', + context: this, + url: url, + beforeSend: function (jqXHR, settings) { + jqXHR.setRequestHeader('X-CSRFToken', '{{ csrf_token }}'); + }, + complete: function(e) { + location.reload() + } + }); + } + + $("#unlink_eng_jira").on('click', function(e) { + jira_action(this,'{% url 'engagement_unlink_jira' eng.id %}') + }); }); {% include 'dojo/snippets/risk_acceptance_actions_snippet_js.html' %}