brynq-sdk-jira 2.0.0__tar.gz → 2.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/PKG-INFO +1 -1
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira/jira.py +31 -1
- brynq_sdk_jira-2.1.0/brynq_sdk_jira/tempo.py +188 -0
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira.egg-info/PKG-INFO +1 -1
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/setup.py +1 -1
- brynq_sdk_jira-2.0.0/brynq_sdk_jira/tempo.py +0 -50
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira/__init__.py +0 -0
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira.egg-info/SOURCES.txt +0 -0
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira.egg-info/dependency_links.txt +0 -0
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira.egg-info/not-zip-safe +0 -0
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira.egg-info/requires.txt +0 -0
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/brynq_sdk_jira.egg-info/top_level.txt +0 -0
- {brynq_sdk_jira-2.0.0 → brynq_sdk_jira-2.1.0}/setup.cfg +0 -0
|
@@ -111,4 +111,34 @@ class Jira(BrynQ):
|
|
|
111
111
|
print(f"Received {len(df)} versions for project {project_key}")
|
|
112
112
|
return df
|
|
113
113
|
else:
|
|
114
|
-
raise ConnectionError(f"Error getting versions from Jira with message: {response.status_code, response.text}")
|
|
114
|
+
raise ConnectionError(f"Error getting versions from Jira with message: {response.status_code, response.text}")
|
|
115
|
+
|
|
116
|
+
def get_users(self) -> pd.DataFrame:
|
|
117
|
+
"""
|
|
118
|
+
This method retrieves users from Jira.
|
|
119
|
+
:return: a dataframe with users
|
|
120
|
+
"""
|
|
121
|
+
start_at = 0
|
|
122
|
+
max_results = 50
|
|
123
|
+
all_users = []
|
|
124
|
+
|
|
125
|
+
while True:
|
|
126
|
+
response = requests.get(f"{self.base_url}rest/api/3/users/search?startAt={start_at}&maxResults={max_results}", headers=self.headers)
|
|
127
|
+
response.raise_for_status()
|
|
128
|
+
if response.status_code == 200:
|
|
129
|
+
users = response.json() # A list of user objects
|
|
130
|
+
all_users.extend(users) # Add users to the total list
|
|
131
|
+
|
|
132
|
+
# Stop if no more users are returned
|
|
133
|
+
if not users:
|
|
134
|
+
break
|
|
135
|
+
|
|
136
|
+
# Increment startAt for the next page
|
|
137
|
+
start_at += len(users)
|
|
138
|
+
else:
|
|
139
|
+
raise ConnectionError(f"Error getting users from Jira with message: {response.status_code, response.text}")
|
|
140
|
+
if self.debug:
|
|
141
|
+
print(f"Received {len(all_users)} jira users from Jira")
|
|
142
|
+
|
|
143
|
+
df = pd.json_normalize(all_users)
|
|
144
|
+
return df
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import requests
|
|
3
|
+
from itertools import islice
|
|
4
|
+
from brynq_sdk_brynq import BrynQ
|
|
5
|
+
from typing import Union, List
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Tempo(BrynQ):
|
|
9
|
+
def __init__(self, label: Union[str, List], debug=False):
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.debug = debug
|
|
12
|
+
credentials = self.get_system_credential(system='tempo-timesheets', label=label)
|
|
13
|
+
self.headers = {
|
|
14
|
+
"Authorization": f"Bearer {credentials['api_token']}",
|
|
15
|
+
"Content-Type": "application/json"
|
|
16
|
+
}
|
|
17
|
+
if self.debug:
|
|
18
|
+
print(self.headers)
|
|
19
|
+
|
|
20
|
+
def get_tempo_hours(self, from_date: str = None, to_date: str = None, updated_from: str = None) -> json:
|
|
21
|
+
"""
|
|
22
|
+
This function gets hours from Tempo for the specified time period
|
|
23
|
+
|
|
24
|
+
:param from_date: (Optional) string - retrieve results starting with this date
|
|
25
|
+
:param to_date: (Optional) string - retrieve results up to and including this date
|
|
26
|
+
:param updated_from: (Optional) string <yyyy-MM-dd['T'HH:mm:ss]['Z']> - retrieve results that have been updated from this date(e.g "2023-11-16") or date time (e.g "2023-11-06T16:48:59Z")
|
|
27
|
+
:return: json response with results
|
|
28
|
+
"""
|
|
29
|
+
total_response = []
|
|
30
|
+
got_all_results = False
|
|
31
|
+
no_of_loops = 0
|
|
32
|
+
parameters = {}
|
|
33
|
+
if from_date is not None:
|
|
34
|
+
parameters.update({"from": from_date})
|
|
35
|
+
if to_date is not None:
|
|
36
|
+
parameters.update({"to": to_date})
|
|
37
|
+
if updated_from is not None:
|
|
38
|
+
parameters.update({"updatedFrom": updated_from})
|
|
39
|
+
|
|
40
|
+
while not got_all_results:
|
|
41
|
+
loop_parameters = parameters | {"limit": 1000, "offset": 1000 * no_of_loops}
|
|
42
|
+
response = requests.get('https://api.tempo.io/4/worklogs', headers=self.headers, params=loop_parameters)
|
|
43
|
+
if response.status_code == 200:
|
|
44
|
+
response_json = response.json()
|
|
45
|
+
no_of_loops += 1
|
|
46
|
+
got_all_results = False if int(response_json['metadata']['count']) == 1000 else True
|
|
47
|
+
total_response += response_json['results']
|
|
48
|
+
else:
|
|
49
|
+
raise ConnectionError(f"Error getting worklogs from Tempo: {response.status_code, response.text}")
|
|
50
|
+
|
|
51
|
+
if self.debug:
|
|
52
|
+
print(f"Received {len(total_response)} lines from Tempo")
|
|
53
|
+
|
|
54
|
+
return total_response
|
|
55
|
+
|
|
56
|
+
def get_tempo_teams(self, team_members: List[str] = None, name: str = None) -> json:
|
|
57
|
+
"""
|
|
58
|
+
Fetches teams from the Tempo API in smaller batches to prevent long URLs if team_members is specified,
|
|
59
|
+
otherwise, retrieves a list of all existing Teams.
|
|
60
|
+
|
|
61
|
+
:param team_members: (Optional) List of Jira user account IDs to filter teams.
|
|
62
|
+
:param name: (Optional) Name of the team to filter teams.
|
|
63
|
+
:return: A json response containing team details.
|
|
64
|
+
"""
|
|
65
|
+
total_response = []
|
|
66
|
+
|
|
67
|
+
# Split team members into smaller chunks (avoid long URLs)
|
|
68
|
+
team_member_chunks = self._chunk_list(team_members, 50) if team_members else [None]
|
|
69
|
+
|
|
70
|
+
for team_chunk in team_member_chunks:
|
|
71
|
+
parameters = {"limit": 1000, "offset": 0}
|
|
72
|
+
if team_chunk:
|
|
73
|
+
parameters["teamMembers"] = ",".join(team_chunk) # Send fewer team members at a time
|
|
74
|
+
if name:
|
|
75
|
+
parameters["name"] = name
|
|
76
|
+
got_all_results = False
|
|
77
|
+
no_of_loops = 0
|
|
78
|
+
|
|
79
|
+
while not got_all_results:
|
|
80
|
+
parameters["offset"] = 1000 * no_of_loops
|
|
81
|
+
response = requests.get('https://api.tempo.io/4/teams', headers=self.headers, params=parameters)
|
|
82
|
+
if response.status_code == 200:
|
|
83
|
+
response_json = response.json()
|
|
84
|
+
total_response.extend(response_json["results"])
|
|
85
|
+
got_all_results = False if int(response_json['metadata']['count']) == 1000 else True
|
|
86
|
+
no_of_loops += 1
|
|
87
|
+
else:
|
|
88
|
+
raise ConnectionError(f"Error getting teams from Tempo: {response.status_code}, {response.text}")
|
|
89
|
+
|
|
90
|
+
if self.debug:
|
|
91
|
+
print(f"Received {len(total_response)} teams from Tempo")
|
|
92
|
+
return total_response
|
|
93
|
+
|
|
94
|
+
def get_tempo_team_members(self, team_ids: List[int]) -> json:
|
|
95
|
+
"""
|
|
96
|
+
Fetches members of multiple teams from the Tempo API iteratively.
|
|
97
|
+
|
|
98
|
+
:param team_ids: List of Tempo team IDs to retrieve members from.
|
|
99
|
+
:return: A json response containing team members' details.
|
|
100
|
+
"""
|
|
101
|
+
total_response = []
|
|
102
|
+
|
|
103
|
+
for team_id in team_ids:
|
|
104
|
+
got_all_results = False
|
|
105
|
+
no_of_loops = 0
|
|
106
|
+
|
|
107
|
+
while not got_all_results:
|
|
108
|
+
parameters = {"limit": 1000, "offset": 1000 * no_of_loops}
|
|
109
|
+
response = requests.get(f'https://api.tempo.io/4/teams/{team_id}/members', headers=self.headers, params=parameters)
|
|
110
|
+
if response.status_code == 200:
|
|
111
|
+
response_json = response.json()
|
|
112
|
+
total_response.extend(response_json["results"])
|
|
113
|
+
got_all_results = False if int(response_json.get('metadata', {}).get('count', 0)) == 1000 else True
|
|
114
|
+
no_of_loops += 1
|
|
115
|
+
else:
|
|
116
|
+
raise ConnectionError(f"Error getting team members from Tempo: {response.status_code}, {response.text}")
|
|
117
|
+
|
|
118
|
+
if self.debug:
|
|
119
|
+
print(f"Received {len(total_response)} team members from Tempo")
|
|
120
|
+
return total_response
|
|
121
|
+
|
|
122
|
+
def get_accounts(self) -> json:
|
|
123
|
+
"""
|
|
124
|
+
Fetches account details from the Tempo API in batches to handle large datasets.
|
|
125
|
+
|
|
126
|
+
:return: A json object containing account details.
|
|
127
|
+
"""
|
|
128
|
+
total_response = []
|
|
129
|
+
got_all_results = False
|
|
130
|
+
no_of_loops = 0
|
|
131
|
+
parameters = {}
|
|
132
|
+
|
|
133
|
+
while not got_all_results:
|
|
134
|
+
loop_parameters = parameters | {"limit": 1000, "offset": 1000 * no_of_loops}
|
|
135
|
+
response = requests.get('https://api.tempo.io/4/accounts', headers=self.headers, params=loop_parameters)
|
|
136
|
+
if response.status_code == 200:
|
|
137
|
+
response_json = response.json()
|
|
138
|
+
no_of_loops += 1
|
|
139
|
+
got_all_results = False if int(response_json['metadata']['count']) == 1000 else True
|
|
140
|
+
total_response += response_json['results']
|
|
141
|
+
else:
|
|
142
|
+
raise ConnectionError(f"Error getting accounts from Tempo: {response.status_code}, {response.text}")
|
|
143
|
+
|
|
144
|
+
if self.debug:
|
|
145
|
+
print(f"Received {len(total_response)} accounts from Tempo")
|
|
146
|
+
return total_response
|
|
147
|
+
|
|
148
|
+
def get_worklog_accounts(self, account_key: str, from_date: str = None, to_date: str = None, updated_from: str = None) -> json:
|
|
149
|
+
"""
|
|
150
|
+
Fetches worklog data for a given account key from the Tempo API.
|
|
151
|
+
|
|
152
|
+
:param account_key: (Required) string - The account key for which worklog data is required.
|
|
153
|
+
:param from_date: (Optional) string - retrieve results starting with this date
|
|
154
|
+
:param to_date: (Optional) string - retrieve results up to and including this date
|
|
155
|
+
:param updated_from: (Optional) string <yyyy-MM-dd['T'HH:mm:ss]['Z']> - retrieve results that have been updated from this date(e.g "2023-11-16") or date time (e.g "2023-11-06T16:48:59Z")
|
|
156
|
+
:return: A json containing worklog details.
|
|
157
|
+
"""
|
|
158
|
+
total_response = []
|
|
159
|
+
got_all_results = False
|
|
160
|
+
no_of_loops = 0
|
|
161
|
+
parameters = {}
|
|
162
|
+
if from_date is not None:
|
|
163
|
+
parameters.update({"from": from_date})
|
|
164
|
+
if to_date is not None:
|
|
165
|
+
parameters.update({"to": to_date})
|
|
166
|
+
if updated_from is not None:
|
|
167
|
+
parameters.update({"updatedFrom": updated_from})
|
|
168
|
+
|
|
169
|
+
while not got_all_results:
|
|
170
|
+
loop_parameters = parameters | {"limit": 1000, "offset": 1000 * no_of_loops}
|
|
171
|
+
response = requests.get(f"https://api.tempo.io/4/worklogs/account/{account_key}", headers=self.headers, params=loop_parameters)
|
|
172
|
+
if response.status_code == 200:
|
|
173
|
+
response_json = response.json()
|
|
174
|
+
total_response.extend(response_json.get("results", []))
|
|
175
|
+
got_all_results = False if int(response_json.get('metadata', {}).get('count', 0)) == 1000 else True
|
|
176
|
+
no_of_loops += 1
|
|
177
|
+
else:
|
|
178
|
+
raise ConnectionError(f"Failed to fetch data for account key {account_key}: {response.status_code}, {response.text}")
|
|
179
|
+
|
|
180
|
+
if self.debug:
|
|
181
|
+
print(f"Received {len(total_response)} worklogs for account key {account_key}")
|
|
182
|
+
|
|
183
|
+
return total_response
|
|
184
|
+
|
|
185
|
+
def _chunk_list(self, data_list, chunk_size):
|
|
186
|
+
"""Splits a list into chunks of `chunk_size`."""
|
|
187
|
+
it = iter(data_list)
|
|
188
|
+
return iter(lambda: list(islice(it, chunk_size)), [])
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import requests
|
|
3
|
-
from typing import Union, List
|
|
4
|
-
from brynq_sdk_brynq import BrynQ
|
|
5
|
-
|
|
6
|
-
class Tempo(BrynQ):
|
|
7
|
-
def __init__(self, label: Union[str, List], debug=False):
|
|
8
|
-
super().__init__()
|
|
9
|
-
self.debug = debug
|
|
10
|
-
credentials = self.get_system_credential(system='tempo-timesheets', label=label)
|
|
11
|
-
self.headers = {
|
|
12
|
-
"Authorization": f"Bearer {credentials['api_token']}",
|
|
13
|
-
"Content-Type": "application/json"
|
|
14
|
-
}
|
|
15
|
-
if self.debug:
|
|
16
|
-
print(self.headers)
|
|
17
|
-
|
|
18
|
-
def get_tempo_hours(self, from_date: str = None, to_date: str = None, updated_from: str = None) -> json:
|
|
19
|
-
"""
|
|
20
|
-
This function gets hours from Tempo for max 8 backs week
|
|
21
|
-
:param from_date:
|
|
22
|
-
:param to_date:
|
|
23
|
-
:return: json response with results
|
|
24
|
-
"""
|
|
25
|
-
total_response = []
|
|
26
|
-
got_all_results = False
|
|
27
|
-
no_of_loops = 0
|
|
28
|
-
parameters = {}
|
|
29
|
-
if from_date is not None:
|
|
30
|
-
parameters.update({"from": from_date})
|
|
31
|
-
if to_date is not None:
|
|
32
|
-
parameters.update({"to": to_date})
|
|
33
|
-
if updated_from is not None:
|
|
34
|
-
parameters.update({"updatedFrom": updated_from})
|
|
35
|
-
|
|
36
|
-
while not got_all_results:
|
|
37
|
-
loop_parameters = parameters | {"limit": 1000, "offset": 1000 * no_of_loops}
|
|
38
|
-
response = requests.get('https://api.tempo.io/4/worklogs', headers=self.headers, params=loop_parameters)
|
|
39
|
-
if response.status_code == 200:
|
|
40
|
-
response_json = response.json()
|
|
41
|
-
no_of_loops += 1
|
|
42
|
-
got_all_results = False if int(response_json['metadata']['count']) == 1000 else True
|
|
43
|
-
total_response += response_json['results']
|
|
44
|
-
else:
|
|
45
|
-
raise ConnectionError(f"Error getting worklogs from Tempo: {response.status_code, response.text}")
|
|
46
|
-
|
|
47
|
-
if self.debug:
|
|
48
|
-
print(f"Received {len(total_response)} lines from Tempo")
|
|
49
|
-
|
|
50
|
-
return total_response
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|