Skip to content

Commit 8055b8d

Browse files
committed
feat: added JIRA ticket creation on self-sched
Closes: #562 Change-Id: I625309a080ab0e3079414c1091ebfef9d3a368a1
1 parent 6936315 commit 8055b8d

File tree

3 files changed

+120
-1
lines changed

3 files changed

+120
-1
lines changed

conf/selfservice.yml

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ ssm_host_limit: 10
66
ssm_default_lifetime: 5
77
# How many clouds (and auth tokens, one per cloud) a unique user ID can have
88
ssm_user_cloud_limit: 2
9+
# Set to true to create a Jira ticket when a self-schedule is created
10+
ssm_jira_create_ticket: false

src/quads/server/blueprints/assignments.py

+49-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import asyncio
12
import re
23
from datetime import datetime
34

45
from flask import Blueprint, Response, jsonify, make_response, request, g
6+
from quads.tools.external.jira import Jira, JiraException
57
from sqlalchemy import inspect
68

79
from quads.config import Config
@@ -301,7 +303,6 @@ def create_self_assignment() -> Response:
301303
kwargs = {
302304
"description": description,
303305
"owner": owner,
304-
"ticket": ticket,
305306
"qinq": qinq,
306307
"wipe": wipe,
307308
"ccuser": cc_user,
@@ -310,6 +311,53 @@ def create_self_assignment() -> Response:
310311
}
311312
if _vlan:
312313
kwargs["vlan_id"] = int(vlan)
314+
315+
create_jira_ticket = Config.get("ssm_jira_create_ticket", False)
316+
if create_jira_ticket:
317+
loop = asyncio.get_event_loop()
318+
try:
319+
jira = Jira(
320+
Config["jira_url"],
321+
loop=loop,
322+
)
323+
except JiraException as ex: # pragma: no cover
324+
response = {
325+
"status_code": 400,
326+
"error": "Bad Request",
327+
"message": f"Jira connection failed: {ex}",
328+
}
329+
return make_response(jsonify(response), 400)
330+
description = ""
331+
for key, value in kwargs.items():
332+
description += f"{key}: {value}\n"
333+
334+
try:
335+
response = loop.run_until_complete(
336+
jira.create_ticket(
337+
summary=f"[SSM] {description}",
338+
description=description,
339+
labels=["SELF-SCHEDULED"],
340+
)
341+
)
342+
except JiraException as ex:
343+
response = {
344+
"status_code": 400,
345+
"error": "Bad Request",
346+
"message": f"Jira ticket creation failed: {ex}",
347+
}
348+
return make_response(jsonify(response), 400)
349+
350+
ticket = response.get("key").split("-")[1]
351+
kwargs["ticket"] = ticket
352+
else:
353+
if not ticket:
354+
response = {
355+
"status_code": 400,
356+
"error": "Bad Request",
357+
"message": "Missing Jira ticket number while automatic ticket creation is disabled",
358+
}
359+
return make_response(jsonify(response), 400)
360+
313361
_assignment_obj = AssignmentDao.create_assignment(**kwargs)
314362
return jsonify(_assignment_obj.as_dict())
315363

src/quads/tools/external/jira.py

+69
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,48 @@ async def put_request(self, endpoint, payload):
120120
logger.error("Resource not found: %s" % self.url + endpoint)
121121
return False
122122

123+
async def create_ticket(self, summary, description, labels=None):
124+
"""Create a Jira ticket."""
125+
if labels is None:
126+
labels = []
127+
endpoint = "/issue/"
128+
logger.debug("POST new ticket")
129+
short_summary = summary.split("\r")
130+
title = f"{short_summary[0]}"
131+
132+
data = {
133+
"fields": {
134+
"project": {"key": self.ticket_queue},
135+
"issuetype": {"name": "Task"},
136+
"summary": title,
137+
"description": description,
138+
}
139+
}
140+
if labels:
141+
data["fields"].update({"labels": labels})
142+
143+
response = await self.post_request(endpoint, data)
144+
return response
145+
146+
async def create_subtask(self, parent_ticket, cloud, description, type_of_subtask):
147+
"""Create a Jira subtask for a specified parent ticket."""
148+
endpoint = "/issue/"
149+
logger.debug("POST new subtask")
150+
title = f"{cloud} {type_of_subtask}"
151+
152+
data = {
153+
"fields": {
154+
"project": {"key": self.ticket_queue},
155+
"issuetype": {"id": "5"},
156+
"parent": {"key": f"{self.ticket_queue}-{parent_ticket}"},
157+
"summary": title,
158+
"labels": [type_of_subtask.upper()],
159+
"description": description,
160+
}
161+
}
162+
response = await self.post_request(endpoint, data)
163+
return response
164+
123165
async def add_watcher(self, ticket, watcher):
124166
issue_id = "%s-%s" % (Config["ticket_queue"], ticket)
125167
endpoint = "/issue/%s/watchers" % issue_id
@@ -203,6 +245,19 @@ async def get_watchers(self, ticket):
203245
return None
204246
return result
205247

248+
async def get_user_by_email(self, email):
249+
"""Find a Jira user by email."""
250+
endpoint = f"/user/search?username={email}"
251+
logger.debug("GET user: %s" % endpoint)
252+
result = await self.get_request(endpoint)
253+
if not result:
254+
logger.error("User not found")
255+
return None
256+
for user in result:
257+
if user.get("emailAddress") == email:
258+
return user
259+
return None
260+
206261
async def get_all_pending_tickets(self):
207262
transition_id = await self.get_transition_id("In Progress")
208263
query = {"status": transition_id}
@@ -246,3 +301,17 @@ async def search_tickets(self, query=None):
246301
logger.error("Failed to get pending tickets")
247302
return None
248303
return result
304+
305+
async def get_field_allowed_values(self, field_id, ticket_id=1):
306+
"""Get list of allowed values from JIRA API for a specified field."""
307+
endpoint = f"/issue/{self.ticket_queue}-{ticket_id}/editmeta"
308+
result = await self.get_request(endpoint)
309+
if not result:
310+
logger.error("Failed to get allowed values")
311+
return None
312+
try:
313+
result = result["fields"][f"customfield_{field_id}"]["allowedValues"]
314+
result = [entry["value"] for entry in result if not entry["value"].startswith("One or more of the")]
315+
except (ValueError, AttributeError):
316+
logger.error("Failed to get allowed values")
317+
return result

0 commit comments

Comments
 (0)