Skip to content

Commit

Permalink
Merge pull request #292 from lsst-ts/tickets/DM-47765
Browse files Browse the repository at this point in the history
Refactor get_jira_obs_report method to account for JIRA REST API user timezone.
  • Loading branch information
sebastian-aranda authored Nov 28, 2024
2 parents 37711f6 + 564a558 commit 6b0b054
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 26 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
Version History
===============

v7.1.6
------

* Refactor get_jira_obs_report method to account for JIRA REST API user timezone. `<https://github.com/lsst-ts/LOVE-manager/pull/292>`_

v7.1.5
------

Expand Down
96 changes: 82 additions & 14 deletions manager/api/tests/test_jira.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import os
import random
from unittest.mock import patch
from urllib.parse import quote

import pytest
import requests
Expand Down Expand Up @@ -78,7 +79,6 @@
class JiraTestCase(TestCase):
def setUp(self):
"""Define the test suite setup."""
# Arrange
shared_params = [
"lfa_files_urls",
"message_text",
Expand Down Expand Up @@ -261,6 +261,12 @@ def setUp(self):
self.jira_request_narrative_full_jira_comment.user = "user"
self.jira_request_narrative_full_jira_comment.get_host = lambda: "localhost"

# headers for jira requests
self.headers = {
"Authorization": f"Basic {os.environ.get('JIRA_API_TOKEN')}",
"content-type": "application/json",
}

def test_missing_parameters(self):
"""Test call to jira_ticket function with missing parameters"""

Expand Down Expand Up @@ -493,50 +499,112 @@ def test_handle_exposure_jira_payload(self):
def test_get_jira_obs_report(self):
"""Test call to get_jira_obs_report
function with all needed parameters"""

# Arrange
mock_jira_patcher = patch("requests.get")
mock_jira_client = mock_jira_patcher.start()
response = requests.Response()
response.status_code = 200
response.json = lambda: {

url_call_1 = (
f"https://{os.environ.get('JIRA_API_HOSTNAME')}/rest/api/latest/myself"
)

response_1 = requests.Response()
response_1.status_code = 200
response_1.json = lambda: {
"timeZone": "America/Phoenix",
}

# American/Phoenix timezone is UTC-7
day_obs = "20241127"
jql_query = (
"project = 'OBS' "
"AND created >= '2024-11-27 05:00' "
"AND created <= '2024-11-28 05:00'"
)
url_call_2 = (
f"https://{os.environ.get('JIRA_API_HOSTNAME')}"
f"/rest/api/latest/search?jql={quote(jql_query)}"
)

response_2 = requests.Response()
response_2.status_code = 200
response_2.json = lambda: {
"issues": [
{
"key": "LOVE-XX",
"fields": {
"summary": "Issue title",
TIME_LOST_FIELD: 13.6,
"creator": {"displayName": "user"},
"created": "2022-07-03T19:58:13.00000",
"created": "2024-11-27T12:00:00.00000",
},
}
]
}
mock_jira_client.return_value = response

mock_jira_client.side_effect = [response_1, response_2]

# Act
request_data = {
"day_obs": 20240902,
"day_obs": day_obs,
}
jira_response = get_jira_obs_report(request_data)

# Assert
mock_jira_client.assert_any_call(url_call_1, headers=self.headers)
mock_jira_client.assert_any_call(url_call_2, headers=self.headers)

assert jira_response[0]["key"] == "LOVE-XX"
assert jira_response[0]["summary"] == "Issue title"
assert jira_response[0]["time_lost"] == 13.6
assert jira_response[0]["reporter"] == "user"
assert jira_response[0]["created"] == "2022-07-03T19:58:13"
assert jira_response[0]["created"] == "2024-11-27T12:00:00"

mock_jira_patcher.stop()

def test_get_jira_obs_report_fail(self):
"""Test call to get_jira_obs_report function with fail response"""

# Arrange
request_data = {
"day_obs": 20241127,
}

mock_jira_patcher = patch("requests.get")
mock_jira_client = mock_jira_patcher.start()
response = requests.Response()
response.status_code = 400
mock_jira_client.return_value = response

request_data = {
"day_obs": 20240902,
success_response_1 = requests.Response()
success_response_1.status_code = 200
success_response_1.json = lambda: {
"timeZone": "America/Phoenix",
}
with pytest.raises(Exception):

failed_response = requests.Response()
failed_response.status_code = 400

# Act
# Incomplete request data
incomplete_request_data = {}
with self.assertRaises(ValueError):
get_jira_obs_report(incomplete_request_data)

# Fail response from Jira to get user data
mock_jira_client.return_value = failed_response
with pytest.raises(Exception) as e:
get_jira_obs_report(request_data)
assert (
str(e.value)
== f"Error getting user timezone from {os.environ.get('JIRA_API_HOSTNAME')}"
)

# Fail response from Jira to get issues
mock_jira_client.side_effect = [success_response_1, failed_response]
with pytest.raises(Exception) as e:
get_jira_obs_report(request_data)
assert (
str(e.value)
== f"Error getting issues from {os.environ.get('JIRA_API_HOSTNAME')}"
)

mock_jira_patcher.stop()

Expand Down
69 changes: 57 additions & 12 deletions manager/manager/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from astropy.units import hour
from django.conf import settings
from django.core.files.storage import Storage
from pytz import timezone
from rest_framework.permissions import BasePermission
from rest_framework.response import Response

Expand Down Expand Up @@ -652,28 +653,72 @@ def handle_jira_payload(request, lfa_urls=[]):


def get_jira_obs_report(request_data):
"""Query all issues of the OBS project for a certain day.
Then get the total observation time loss from the time_lost param
"""
"""Connect to the Rubin Observatory JIRA Cloud REST API to
query all issues of the OBS project for a certain obs day.
For more information on the REST API endpoints refer to:
- https://developer.atlassian.com/cloud/jira/platform/rest/v3
- https://developer.atlassian.com/cloud/jira/platform/\
basic-auth-for-rest-apis/
Parameters
----------
request_data : `dict`
The request data
Notes
-----
The JIRA REST API query is based on the user timezone so
we need to account for the timezone difference between the user and the
server. The user timezone is obtained from the JIRA API.
Returns
-------
List
List of dictionaries containing the following keys:
- key: The issue key
- summary: The issue summary
- time_lost: The time lost in the issue
- reporter: The issue reporter
- created: The issue creation date
"""
intitial_day_obs_tai = get_obsday_to_tai(request_data.get("day_obs"))
final_day_obs_tai = intitial_day_obs_tai + timedelta(days=1)

initial_day_obs_string = intitial_day_obs_tai.strftime("%Y-%m-%d")
final_day_obs_string = final_day_obs_tai.strftime("%Y-%m-%d")
headers = {
"Authorization": f"Basic {os.environ.get('JIRA_API_TOKEN')}",
"content-type": "application/json",
}

# Get user timezone
url = f"https://{os.environ.get('JIRA_API_HOSTNAME')}/rest/api/latest/myself"
response = requests.get(url, headers=headers)
if response.status_code == 200:
user_timezone = timezone(response.json()["timeZone"])
else:
raise Exception(
f"Error getting user timezone from {os.environ.get('JIRA_API_HOSTNAME')}"
)

start_date_user_datetime = intitial_day_obs_tai.replace(
tzinfo=timezone("UTC")
).astimezone(user_timezone)
end_date_user_datetime = final_day_obs_tai.replace(
tzinfo=timezone("UTC")
).astimezone(user_timezone)

initial_day_obs_string = start_date_user_datetime.strftime("%Y-%m-%d")
final_day_obs_string = end_date_user_datetime.strftime("%Y-%m-%d")
start_date_user_time_string = start_date_user_datetime.time().strftime("%H:%M")
end_date_user_time_string = end_date_user_datetime.time().strftime("%H:%M")

# JQL query to find issues created on a specific date
jql_query = (
f"project = 'OBS' "
f"AND created >= '{initial_day_obs_string} 12:00' "
f"AND created <= '{final_day_obs_string} 12:00'"
f"AND created >= '{initial_day_obs_string} {start_date_user_time_string}' "
f"AND created <= '{final_day_obs_string} {end_date_user_time_string}'"
)

headers = {
"Authorization": f"Basic {os.environ.get('JIRA_API_TOKEN')}",
"content-type": "application/json",
}

url = f"https://{os.environ.get('JIRA_API_HOSTNAME')}/rest/api/latest/search?jql={quote(jql_query)}"
response = requests.get(url, headers=headers)
if response.status_code == 200:
Expand Down

0 comments on commit 6b0b054

Please sign in to comment.