Skip to content

Commit

Permalink
feat: added JIRA ticket creation on self-sched
Browse files Browse the repository at this point in the history
Closes: #562
Change-Id: I625309a080ab0e3079414c1091ebfef9d3a368a1
  • Loading branch information
grafuls committed Jan 15, 2025
1 parent 6936315 commit 8055b8d
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 1 deletion.
2 changes: 2 additions & 0 deletions conf/selfservice.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ ssm_host_limit: 10
ssm_default_lifetime: 5
# How many clouds (and auth tokens, one per cloud) a unique user ID can have
ssm_user_cloud_limit: 2
# Set to true to create a Jira ticket when a self-schedule is created
ssm_jira_create_ticket: false
50 changes: 49 additions & 1 deletion src/quads/server/blueprints/assignments.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import asyncio
import re
from datetime import datetime

from flask import Blueprint, Response, jsonify, make_response, request, g
from quads.tools.external.jira import Jira, JiraException
from sqlalchemy import inspect

from quads.config import Config
Expand Down Expand Up @@ -301,7 +303,6 @@ def create_self_assignment() -> Response:
kwargs = {
"description": description,
"owner": owner,
"ticket": ticket,
"qinq": qinq,
"wipe": wipe,
"ccuser": cc_user,
Expand All @@ -310,6 +311,53 @@ def create_self_assignment() -> Response:
}
if _vlan:
kwargs["vlan_id"] = int(vlan)

create_jira_ticket = Config.get("ssm_jira_create_ticket", False)
if create_jira_ticket:
loop = asyncio.get_event_loop()
try:
jira = Jira(
Config["jira_url"],
loop=loop,
)
except JiraException as ex: # pragma: no cover
response = {
"status_code": 400,
"error": "Bad Request",
"message": f"Jira connection failed: {ex}",
}
return make_response(jsonify(response), 400)
description = ""
for key, value in kwargs.items():
description += f"{key}: {value}\n"

try:
response = loop.run_until_complete(
jira.create_ticket(
summary=f"[SSM] {description}",
description=description,
labels=["SELF-SCHEDULED"],
)
)
except JiraException as ex:
response = {
"status_code": 400,
"error": "Bad Request",
"message": f"Jira ticket creation failed: {ex}",
}
return make_response(jsonify(response), 400)

ticket = response.get("key").split("-")[1]
kwargs["ticket"] = ticket
else:
if not ticket:
response = {
"status_code": 400,
"error": "Bad Request",
"message": "Missing Jira ticket number while automatic ticket creation is disabled",
}
return make_response(jsonify(response), 400)

_assignment_obj = AssignmentDao.create_assignment(**kwargs)
return jsonify(_assignment_obj.as_dict())

Expand Down
69 changes: 69 additions & 0 deletions src/quads/tools/external/jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,48 @@ async def put_request(self, endpoint, payload):
logger.error("Resource not found: %s" % self.url + endpoint)
return False

async def create_ticket(self, summary, description, labels=None):
"""Create a Jira ticket."""
if labels is None:
labels = []
endpoint = "/issue/"
logger.debug("POST new ticket")
short_summary = summary.split("\r")
title = f"{short_summary[0]}"

data = {
"fields": {
"project": {"key": self.ticket_queue},
"issuetype": {"name": "Task"},
"summary": title,
"description": description,
}
}
if labels:
data["fields"].update({"labels": labels})

response = await self.post_request(endpoint, data)
return response

async def create_subtask(self, parent_ticket, cloud, description, type_of_subtask):
"""Create a Jira subtask for a specified parent ticket."""
endpoint = "/issue/"
logger.debug("POST new subtask")
title = f"{cloud} {type_of_subtask}"

data = {
"fields": {
"project": {"key": self.ticket_queue},
"issuetype": {"id": "5"},
"parent": {"key": f"{self.ticket_queue}-{parent_ticket}"},
"summary": title,
"labels": [type_of_subtask.upper()],
"description": description,
}
}
response = await self.post_request(endpoint, data)
return response

async def add_watcher(self, ticket, watcher):
issue_id = "%s-%s" % (Config["ticket_queue"], ticket)
endpoint = "/issue/%s/watchers" % issue_id
Expand Down Expand Up @@ -203,6 +245,19 @@ async def get_watchers(self, ticket):
return None
return result

async def get_user_by_email(self, email):
"""Find a Jira user by email."""
endpoint = f"/user/search?username={email}"
logger.debug("GET user: %s" % endpoint)
result = await self.get_request(endpoint)
if not result:
logger.error("User not found")
return None
for user in result:
if user.get("emailAddress") == email:
return user
return None

async def get_all_pending_tickets(self):
transition_id = await self.get_transition_id("In Progress")
query = {"status": transition_id}
Expand Down Expand Up @@ -246,3 +301,17 @@ async def search_tickets(self, query=None):
logger.error("Failed to get pending tickets")
return None
return result

async def get_field_allowed_values(self, field_id, ticket_id=1):
"""Get list of allowed values from JIRA API for a specified field."""
endpoint = f"/issue/{self.ticket_queue}-{ticket_id}/editmeta"
result = await self.get_request(endpoint)
if not result:
logger.error("Failed to get allowed values")
return None
try:
result = result["fields"][f"customfield_{field_id}"]["allowedValues"]
result = [entry["value"] for entry in result if not entry["value"].startswith("One or more of the")]
except (ValueError, AttributeError):
logger.error("Failed to get allowed values")
return result

0 comments on commit 8055b8d

Please sign in to comment.