Skip to content
This repository was archived by the owner on Sep 3, 2025. It is now read-only.

Commit e8c424f

Browse files
authored
Creating fallback in case Jira fails (#5415)
1 parent 624110b commit e8c424f

File tree

4 files changed

+104
-49
lines changed

4 files changed

+104
-49
lines changed

src/dispatch/incident/flows.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def incident_create_resources(
157157
) -> Incident:
158158
"""Creates all resources required for incidents."""
159159
# we create the incident ticket
160-
if not incident.ticket:
160+
if not incident.ticket or incident.ticket.resource_type == "jira-error-ticket":
161161
ticket_flows.create_incident_ticket(incident=incident, db_session=db_session)
162162

163163
# we update the channel name immediately for dedicated channel cases escalated -> incident

src/dispatch/plugins/dispatch_jira/plugin.py

Lines changed: 91 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@
77

88
from typing import Any
99
import json
10+
import logging
1011

1112
import requests
1213
from requests.auth import HTTPBasicAuth
1314

15+
from sqlalchemy.orm import Session
16+
1417
from pydantic import Field, SecretStr, AnyHttpUrl
1518

1619
from jinja2 import Template
@@ -20,14 +23,21 @@
2023
from dispatch.decorators import apply, counter, timer
2124
from dispatch.enums import DispatchEnum
2225
from dispatch.plugins import dispatch_jira as jira_plugin
26+
from dispatch.case import service as case_service
27+
from dispatch.incident import service as incident_service
2328
from dispatch.plugins.bases import TicketPlugin
29+
from dispatch.project.models import Project
2430

2531
from .templates import (
2632
CASE_ISSUE_SUMMARY_TEMPLATE,
2733
INCIDENT_ISSUE_SUMMARY_NO_RESOURCES_TEMPLATE,
2834
INCIDENT_ISSUE_SUMMARY_TEMPLATE,
2935
)
3036

37+
from dispatch.config import DISPATCH_UI_URL
38+
39+
log = logging.getLogger(__name__)
40+
3141

3242
class HostingType(DispatchEnum):
3343
"""Type of Jira deployment."""
@@ -261,6 +271,16 @@ def update(
261271
return data
262272

263273

274+
def create_fallback_ticket(id: int, project: Project, db_session: Session):
275+
resource_id = f"dispatch-{project.organization.slug}-{project.slug}-{id}"
276+
277+
return {
278+
"resource_id": resource_id,
279+
"weblink": f"{DISPATCH_UI_URL}/{project.organization.name}/incidents/{resource_id}?project={project.name}",
280+
"resource_type": "jira-error-ticket",
281+
}
282+
283+
264284
@apply(counter, exclude=["__init__"])
265285
@apply(timer, exclude=["__init__"])
266286
class JiraTicketPlugin(TicketPlugin):
@@ -285,38 +305,51 @@ def create(
285305
db_session=None,
286306
):
287307
"""Creates an incident Jira issue."""
288-
client = create_client(self.configuration)
308+
try:
309+
client = create_client(self.configuration)
289310

290-
assignee = get_user_field(client, self.configuration, commander_email)
311+
assignee = get_user_field(client, self.configuration, commander_email)
291312

292-
reporter = assignee
293-
if reporter_email != commander_email:
294-
reporter = get_user_field(client, self.configuration, reporter_email)
313+
reporter = assignee
314+
if reporter_email != commander_email:
315+
reporter = get_user_field(client, self.configuration, reporter_email)
295316

296-
project_id, issue_type_name = process_plugin_metadata(incident_type_plugin_metadata)
317+
project_id, issue_type_name = process_plugin_metadata(incident_type_plugin_metadata)
297318

298-
if not project_id:
299-
project_id = self.configuration.default_project_id
319+
if not project_id:
320+
project_id = self.configuration.default_project_id
300321

301-
# NOTE: to support issue creation by project id or key
302-
project = {"id": project_id}
303-
if not project_id.isdigit():
304-
project = {"key": project_id}
322+
# NOTE: to support issue creation by project id or key
323+
project = {"id": project_id}
324+
if not project_id.isdigit():
325+
project = {"key": project_id}
305326

306-
if not issue_type_name:
307-
issue_type_name = self.configuration.default_issue_type_name
327+
if not issue_type_name:
328+
issue_type_name = self.configuration.default_issue_type_name
308329

309-
issuetype = {"name": issue_type_name}
330+
issuetype = {"name": issue_type_name}
310331

311-
issue_fields = {
312-
"project": project,
313-
"issuetype": issuetype,
314-
"assignee": assignee,
315-
"reporter": reporter,
316-
"summary": title,
317-
}
332+
issue_fields = {
333+
"project": project,
334+
"issuetype": issuetype,
335+
"assignee": assignee,
336+
"reporter": reporter,
337+
"summary": title,
338+
}
339+
340+
ticket = create(self.configuration, client, issue_fields)
341+
except Exception as e:
342+
log.exception(
343+
f"Failed to create Jira ticket for incident_id: {incident_id}. "
344+
f"Creating incident ticket with core plugin instead. Error: {e}"
345+
)
346+
# fall back to creating a ticket without the plugin
347+
incident = incident_service.get(db_session=db_session, incident_id=incident_id)
348+
ticket = create_fallback_ticket(
349+
id=incident.id, project=incident.project, db_session=db_session
350+
)
318351

319-
return create(self.configuration, client, issue_fields)
352+
return ticket
320353

321354
def update(
322355
self,
@@ -378,36 +411,49 @@ def create_case_ticket(
378411
db_session=None,
379412
):
380413
"""Creates a case Jira issue."""
381-
client = create_client(self.configuration)
414+
try:
415+
client = create_client(self.configuration)
382416

383-
assignee = get_user_field(client, self.configuration, assignee_email)
384-
# TODO(mvilanova): enable reporter email and replace assignee email
385-
# reporter = get_user_field(client, self.configuration, reporter_email)
386-
reporter = assignee
417+
assignee = get_user_field(client, self.configuration, assignee_email)
418+
# TODO(mvilanova): enable reporter email and replace assignee email
419+
# reporter = get_user_field(client, self.configuration, reporter_email)
420+
reporter = assignee
387421

388-
project_id, issue_type_name = process_plugin_metadata(case_type_plugin_metadata)
422+
project_id, issue_type_name = process_plugin_metadata(case_type_plugin_metadata)
389423

390-
if not project_id:
391-
project_id = self.configuration.default_project_id
424+
if not project_id:
425+
project_id = self.configuration.default_project_id
392426

393-
project = {"id": project_id}
394-
if not project_id.isdigit():
395-
project = {"key": project_id}
427+
project = {"id": project_id}
428+
if not project_id.isdigit():
429+
project = {"key": project_id}
396430

397-
if not issue_type_name:
398-
issue_type_name = self.configuration.default_issue_type_name
431+
if not issue_type_name:
432+
issue_type_name = self.configuration.default_issue_type_name
399433

400-
issuetype = {"name": issue_type_name}
434+
issuetype = {"name": issue_type_name}
401435

402-
issue_fields = {
403-
"project": project,
404-
"issuetype": issuetype,
405-
"assignee": assignee,
406-
"reporter": reporter,
407-
"summary": title,
408-
}
436+
issue_fields = {
437+
"project": project,
438+
"issuetype": issuetype,
439+
"assignee": assignee,
440+
"reporter": reporter,
441+
"summary": title,
442+
}
409443

410-
return create(self.configuration, client, issue_fields)
444+
ticket = create(self.configuration, client, issue_fields)
445+
except Exception as e:
446+
log.exception(
447+
(
448+
f"Failed to create Jira ticket for case_id: {case_id}. "
449+
f"Creating case ticket with core plugin instead. Error: {e}"
450+
)
451+
)
452+
# fall back to creating a ticket without the plugin
453+
case = case_service.get(db_session=db_session, case_id=case_id)
454+
ticket = create_fallback_ticket(id=case.id, project=case.project, db_session=db_session)
455+
456+
return ticket
411457

412458
def create_task_ticket(
413459
self,

src/dispatch/static/dispatch/src/incident/ResourcesTab.vue

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@
33
<v-list-item v-if="ticket" :href="ticket.weblink" target="_blank" class="my-3">
44
<v-list-item-title>Ticket</v-list-item-title>
55
<v-list-item-subtitle>{{ ticket.description }}</v-list-item-subtitle>
6-
6+
<div
7+
class="text-caption"
8+
v-if="(!ticket || ticket.resource_type == 'jira-error-ticket') && ticketPluginEnabled"
9+
>
10+
<span style="color: red">Ticket error: </span>
11+
<span>
12+
No Jira ticket created. Use Recreate Missing Resources to regenerate the ticket.
13+
</span>
14+
</div>
715
<template #append>
816
<v-icon>mdi-open-in-new</v-icon>
917
</template>
@@ -49,7 +57,7 @@
4957
</span>
5058
<span
5159
v-if="
52-
(!ticket && ticketPluginEnabled) ||
60+
((!ticket || ticket.resource_type == 'jira-error-ticket') && ticketPluginEnabled) ||
5361
(!conference && conferencePluginEnabled) ||
5462
(!conversation && conversationPluginEnabled) ||
5563
(!storage && storagePluginEnabled) ||

src/dispatch/ticket/flows.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def create_incident_ticket(incident: Incident, db_session: Session):
5959
log.error(f"Incident ticket not created. Plugin {plugin.plugin.slug} encountered an error.")
6060
return
6161

62-
external_ticket.update({"resource_type": plugin.plugin.slug})
62+
if "resource_type" not in external_ticket:
63+
external_ticket.update({"resource_type": plugin.plugin.slug})
6364

6465
# we create the internal incident ticket
6566
ticket_in = TicketCreate(**external_ticket)

0 commit comments

Comments
 (0)