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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq_sdk_jira
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: JIRA wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 1.0
2
2
  Name: brynq-sdk-jira
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: JIRA wrapper from BrynQ
5
5
  Home-page: UNKNOWN
6
6
  Author: BrynQ
@@ -2,7 +2,7 @@ from setuptools import setup, find_namespace_packages
2
2
 
3
3
  setup(
4
4
  name='brynq_sdk_jira',
5
- version='2.0.0',
5
+ version='2.1.0',
6
6
  description='JIRA wrapper from BrynQ',
7
7
  long_description='JIRA wrapper from BrynQ',
8
8
  author='BrynQ',
@@ -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