Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(plugin): create fallback ticket in case Jira fails #5415

Merged
merged 2 commits into from
Nov 4, 2024
Merged
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 src/dispatch/incident/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ def incident_create_resources(
) -> Incident:
"""Creates all resources required for incidents."""
# we create the incident ticket
if not incident.ticket:
if not incident.ticket or incident.ticket.resource_type == "jira-error-ticket":
ticket_flows.create_incident_ticket(incident=incident, db_session=db_session)

# we update the channel name immediately for dedicated channel cases escalated -> incident
Expand Down
136 changes: 91 additions & 45 deletions src/dispatch/plugins/dispatch_jira/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

from typing import Any
import json
import logging

import requests
from requests.auth import HTTPBasicAuth

from sqlalchemy.orm import Session

from pydantic import Field, SecretStr, AnyHttpUrl

from jinja2 import Template
Expand All @@ -20,14 +23,21 @@
from dispatch.decorators import apply, counter, timer
from dispatch.enums import DispatchEnum
from dispatch.plugins import dispatch_jira as jira_plugin
from dispatch.case import service as case_service
from dispatch.incident import service as incident_service
from dispatch.plugins.bases import TicketPlugin
from dispatch.project.models import Project

from .templates import (
CASE_ISSUE_SUMMARY_TEMPLATE,
INCIDENT_ISSUE_SUMMARY_NO_RESOURCES_TEMPLATE,
INCIDENT_ISSUE_SUMMARY_TEMPLATE,
)

from dispatch.config import DISPATCH_UI_URL

log = logging.getLogger(__name__)


class HostingType(DispatchEnum):
"""Type of Jira deployment."""
Expand Down Expand Up @@ -261,6 +271,16 @@ def update(
return data


def create_fallback_ticket(id: int, project: Project, db_session: Session):
resource_id = f"dispatch-{project.organization.slug}-{project.slug}-{id}"

return {
"resource_id": resource_id,
"weblink": f"{DISPATCH_UI_URL}/{project.organization.name}/incidents/{resource_id}?project={project.name}",
"resource_type": "jira-error-ticket",
}


@apply(counter, exclude=["__init__"])
@apply(timer, exclude=["__init__"])
class JiraTicketPlugin(TicketPlugin):
Expand All @@ -285,38 +305,51 @@ def create(
db_session=None,
):
"""Creates an incident Jira issue."""
client = create_client(self.configuration)
try:
client = create_client(self.configuration)

assignee = get_user_field(client, self.configuration, commander_email)
assignee = get_user_field(client, self.configuration, commander_email)

reporter = assignee
if reporter_email != commander_email:
reporter = get_user_field(client, self.configuration, reporter_email)
reporter = assignee
if reporter_email != commander_email:
reporter = get_user_field(client, self.configuration, reporter_email)

project_id, issue_type_name = process_plugin_metadata(incident_type_plugin_metadata)
project_id, issue_type_name = process_plugin_metadata(incident_type_plugin_metadata)

if not project_id:
project_id = self.configuration.default_project_id
if not project_id:
project_id = self.configuration.default_project_id

# NOTE: to support issue creation by project id or key
project = {"id": project_id}
if not project_id.isdigit():
project = {"key": project_id}
# NOTE: to support issue creation by project id or key
project = {"id": project_id}
if not project_id.isdigit():
project = {"key": project_id}

if not issue_type_name:
issue_type_name = self.configuration.default_issue_type_name
if not issue_type_name:
issue_type_name = self.configuration.default_issue_type_name

issuetype = {"name": issue_type_name}
issuetype = {"name": issue_type_name}

issue_fields = {
"project": project,
"issuetype": issuetype,
"assignee": assignee,
"reporter": reporter,
"summary": title,
}
issue_fields = {
"project": project,
"issuetype": issuetype,
"assignee": assignee,
"reporter": reporter,
"summary": title,
}

ticket = create(self.configuration, client, issue_fields)
except Exception as e:
log.exception(
f"Failed to create Jira ticket for incident_id: {incident_id}. "
f"Creating incident ticket with core plugin instead. Error: {e}"
)
# fall back to creating a ticket without the plugin
incident = incident_service.get(db_session=db_session, incident_id=incident_id)
ticket = create_fallback_ticket(
id=incident.id, project=incident.project, db_session=db_session
)

return create(self.configuration, client, issue_fields)
return ticket

def update(
self,
Expand Down Expand Up @@ -378,36 +411,49 @@ def create_case_ticket(
db_session=None,
):
"""Creates a case Jira issue."""
client = create_client(self.configuration)
try:
client = create_client(self.configuration)

assignee = get_user_field(client, self.configuration, assignee_email)
# TODO(mvilanova): enable reporter email and replace assignee email
# reporter = get_user_field(client, self.configuration, reporter_email)
reporter = assignee
assignee = get_user_field(client, self.configuration, assignee_email)
# TODO(mvilanova): enable reporter email and replace assignee email
# reporter = get_user_field(client, self.configuration, reporter_email)
reporter = assignee

project_id, issue_type_name = process_plugin_metadata(case_type_plugin_metadata)
project_id, issue_type_name = process_plugin_metadata(case_type_plugin_metadata)

if not project_id:
project_id = self.configuration.default_project_id
if not project_id:
project_id = self.configuration.default_project_id

project = {"id": project_id}
if not project_id.isdigit():
project = {"key": project_id}
project = {"id": project_id}
if not project_id.isdigit():
project = {"key": project_id}

if not issue_type_name:
issue_type_name = self.configuration.default_issue_type_name
if not issue_type_name:
issue_type_name = self.configuration.default_issue_type_name

issuetype = {"name": issue_type_name}
issuetype = {"name": issue_type_name}

issue_fields = {
"project": project,
"issuetype": issuetype,
"assignee": assignee,
"reporter": reporter,
"summary": title,
}
issue_fields = {
"project": project,
"issuetype": issuetype,
"assignee": assignee,
"reporter": reporter,
"summary": title,
}

return create(self.configuration, client, issue_fields)
ticket = create(self.configuration, client, issue_fields)
except Exception as e:
log.exception(
(
f"Failed to create Jira ticket for case_id: {case_id}. "
f"Creating case ticket with core plugin instead. Error: {e}"
)
)
# fall back to creating a ticket without the plugin
case = case_service.get(db_session=db_session, case_id=case_id)
ticket = create_fallback_ticket(id=case.id, project=case.project, db_session=db_session)

return ticket

def create_task_ticket(
self,
Expand Down
12 changes: 10 additions & 2 deletions src/dispatch/static/dispatch/src/incident/ResourcesTab.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@
<v-list-item v-if="ticket" :href="ticket.weblink" target="_blank" class="my-3">
<v-list-item-title>Ticket</v-list-item-title>
<v-list-item-subtitle>{{ ticket.description }}</v-list-item-subtitle>

<div
class="text-caption"
v-if="(!ticket || ticket.resource_type == 'jira-error-ticket') && ticketPluginEnabled"
>
<span style="color: red">Ticket error: </span>
<span>
No Jira ticket created. Use Recreate Missing Resources to regenerate the ticket.
</span>
</div>
<template #append>
<v-icon>mdi-open-in-new</v-icon>
</template>
Expand Down Expand Up @@ -49,7 +57,7 @@
</span>
<span
v-if="
(!ticket && ticketPluginEnabled) ||
((!ticket || ticket.resource_type == 'jira-error-ticket') && ticketPluginEnabled) ||
(!conference && conferencePluginEnabled) ||
(!conversation && conversationPluginEnabled) ||
(!storage && storagePluginEnabled) ||
Expand Down
3 changes: 2 additions & 1 deletion src/dispatch/ticket/flows.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ def create_incident_ticket(incident: Incident, db_session: Session):
log.error(f"Incident ticket not created. Plugin {plugin.plugin.slug} encountered an error.")
return

external_ticket.update({"resource_type": plugin.plugin.slug})
if "resource_type" not in external_ticket:
external_ticket.update({"resource_type": plugin.plugin.slug})

# we create the internal incident ticket
ticket_in = TicketCreate(**external_ticket)
Expand Down
Loading