ingestr 0.13.2__py3-none-any.whl → 0.14.104__py3-none-any.whl

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.
Files changed (146) hide show
  1. ingestr/conftest.py +72 -0
  2. ingestr/main.py +134 -87
  3. ingestr/src/adjust/__init__.py +4 -4
  4. ingestr/src/adjust/adjust_helpers.py +7 -3
  5. ingestr/src/airtable/__init__.py +3 -2
  6. ingestr/src/allium/__init__.py +128 -0
  7. ingestr/src/anthropic/__init__.py +277 -0
  8. ingestr/src/anthropic/helpers.py +525 -0
  9. ingestr/src/applovin/__init__.py +262 -0
  10. ingestr/src/applovin_max/__init__.py +117 -0
  11. ingestr/src/appsflyer/__init__.py +325 -0
  12. ingestr/src/appsflyer/client.py +49 -45
  13. ingestr/src/appstore/__init__.py +1 -0
  14. ingestr/src/arrow/__init__.py +9 -1
  15. ingestr/src/asana_source/__init__.py +1 -1
  16. ingestr/src/attio/__init__.py +102 -0
  17. ingestr/src/attio/helpers.py +65 -0
  18. ingestr/src/blob.py +38 -11
  19. ingestr/src/buildinfo.py +1 -0
  20. ingestr/src/chess/__init__.py +1 -1
  21. ingestr/src/clickup/__init__.py +85 -0
  22. ingestr/src/clickup/helpers.py +47 -0
  23. ingestr/src/collector/spinner.py +43 -0
  24. ingestr/src/couchbase_source/__init__.py +118 -0
  25. ingestr/src/couchbase_source/helpers.py +135 -0
  26. ingestr/src/cursor/__init__.py +83 -0
  27. ingestr/src/cursor/helpers.py +188 -0
  28. ingestr/src/destinations.py +520 -33
  29. ingestr/src/docebo/__init__.py +589 -0
  30. ingestr/src/docebo/client.py +435 -0
  31. ingestr/src/docebo/helpers.py +97 -0
  32. ingestr/src/elasticsearch/__init__.py +80 -0
  33. ingestr/src/elasticsearch/helpers.py +138 -0
  34. ingestr/src/errors.py +8 -0
  35. ingestr/src/facebook_ads/__init__.py +47 -28
  36. ingestr/src/facebook_ads/helpers.py +59 -37
  37. ingestr/src/facebook_ads/settings.py +2 -0
  38. ingestr/src/facebook_ads/utils.py +39 -0
  39. ingestr/src/factory.py +116 -2
  40. ingestr/src/filesystem/__init__.py +8 -3
  41. ingestr/src/filters.py +46 -3
  42. ingestr/src/fluxx/__init__.py +9906 -0
  43. ingestr/src/fluxx/helpers.py +209 -0
  44. ingestr/src/frankfurter/__init__.py +157 -0
  45. ingestr/src/frankfurter/helpers.py +48 -0
  46. ingestr/src/freshdesk/__init__.py +89 -0
  47. ingestr/src/freshdesk/freshdesk_client.py +137 -0
  48. ingestr/src/freshdesk/settings.py +9 -0
  49. ingestr/src/fundraiseup/__init__.py +95 -0
  50. ingestr/src/fundraiseup/client.py +81 -0
  51. ingestr/src/github/__init__.py +41 -6
  52. ingestr/src/github/helpers.py +5 -5
  53. ingestr/src/google_analytics/__init__.py +22 -4
  54. ingestr/src/google_analytics/helpers.py +124 -6
  55. ingestr/src/google_sheets/__init__.py +4 -4
  56. ingestr/src/google_sheets/helpers/data_processing.py +2 -2
  57. ingestr/src/hostaway/__init__.py +302 -0
  58. ingestr/src/hostaway/client.py +288 -0
  59. ingestr/src/http/__init__.py +35 -0
  60. ingestr/src/http/readers.py +114 -0
  61. ingestr/src/http_client.py +24 -0
  62. ingestr/src/hubspot/__init__.py +66 -23
  63. ingestr/src/hubspot/helpers.py +52 -22
  64. ingestr/src/hubspot/settings.py +14 -7
  65. ingestr/src/influxdb/__init__.py +46 -0
  66. ingestr/src/influxdb/client.py +34 -0
  67. ingestr/src/intercom/__init__.py +142 -0
  68. ingestr/src/intercom/helpers.py +674 -0
  69. ingestr/src/intercom/settings.py +279 -0
  70. ingestr/src/isoc_pulse/__init__.py +159 -0
  71. ingestr/src/jira_source/__init__.py +340 -0
  72. ingestr/src/jira_source/helpers.py +439 -0
  73. ingestr/src/jira_source/settings.py +170 -0
  74. ingestr/src/kafka/__init__.py +4 -1
  75. ingestr/src/kinesis/__init__.py +139 -0
  76. ingestr/src/kinesis/helpers.py +82 -0
  77. ingestr/src/klaviyo/{_init_.py → __init__.py} +5 -6
  78. ingestr/src/linear/__init__.py +634 -0
  79. ingestr/src/linear/helpers.py +111 -0
  80. ingestr/src/linkedin_ads/helpers.py +0 -1
  81. ingestr/src/loader.py +69 -0
  82. ingestr/src/mailchimp/__init__.py +126 -0
  83. ingestr/src/mailchimp/helpers.py +226 -0
  84. ingestr/src/mailchimp/settings.py +164 -0
  85. ingestr/src/masking.py +344 -0
  86. ingestr/src/mixpanel/__init__.py +62 -0
  87. ingestr/src/mixpanel/client.py +99 -0
  88. ingestr/src/monday/__init__.py +246 -0
  89. ingestr/src/monday/helpers.py +392 -0
  90. ingestr/src/monday/settings.py +328 -0
  91. ingestr/src/mongodb/__init__.py +72 -8
  92. ingestr/src/mongodb/helpers.py +915 -38
  93. ingestr/src/partition.py +32 -0
  94. ingestr/src/personio/__init__.py +331 -0
  95. ingestr/src/personio/helpers.py +86 -0
  96. ingestr/src/phantombuster/__init__.py +65 -0
  97. ingestr/src/phantombuster/client.py +87 -0
  98. ingestr/src/pinterest/__init__.py +82 -0
  99. ingestr/src/pipedrive/__init__.py +198 -0
  100. ingestr/src/pipedrive/helpers/__init__.py +23 -0
  101. ingestr/src/pipedrive/helpers/custom_fields_munger.py +102 -0
  102. ingestr/src/pipedrive/helpers/pages.py +115 -0
  103. ingestr/src/pipedrive/settings.py +27 -0
  104. ingestr/src/pipedrive/typing.py +3 -0
  105. ingestr/src/plusvibeai/__init__.py +335 -0
  106. ingestr/src/plusvibeai/helpers.py +544 -0
  107. ingestr/src/plusvibeai/settings.py +252 -0
  108. ingestr/src/quickbooks/__init__.py +117 -0
  109. ingestr/src/resource.py +40 -0
  110. ingestr/src/revenuecat/__init__.py +83 -0
  111. ingestr/src/revenuecat/helpers.py +237 -0
  112. ingestr/src/salesforce/__init__.py +156 -0
  113. ingestr/src/salesforce/helpers.py +64 -0
  114. ingestr/src/shopify/__init__.py +1 -17
  115. ingestr/src/smartsheets/__init__.py +82 -0
  116. ingestr/src/snapchat_ads/__init__.py +489 -0
  117. ingestr/src/snapchat_ads/client.py +72 -0
  118. ingestr/src/snapchat_ads/helpers.py +535 -0
  119. ingestr/src/socrata_source/__init__.py +83 -0
  120. ingestr/src/socrata_source/helpers.py +85 -0
  121. ingestr/src/socrata_source/settings.py +8 -0
  122. ingestr/src/solidgate/__init__.py +219 -0
  123. ingestr/src/solidgate/helpers.py +154 -0
  124. ingestr/src/sources.py +3132 -212
  125. ingestr/src/stripe_analytics/__init__.py +49 -21
  126. ingestr/src/stripe_analytics/helpers.py +286 -1
  127. ingestr/src/stripe_analytics/settings.py +62 -10
  128. ingestr/src/telemetry/event.py +10 -9
  129. ingestr/src/tiktok_ads/__init__.py +12 -6
  130. ingestr/src/tiktok_ads/tiktok_helpers.py +0 -1
  131. ingestr/src/trustpilot/__init__.py +48 -0
  132. ingestr/src/trustpilot/client.py +48 -0
  133. ingestr/src/version.py +6 -1
  134. ingestr/src/wise/__init__.py +68 -0
  135. ingestr/src/wise/client.py +63 -0
  136. ingestr/src/zoom/__init__.py +99 -0
  137. ingestr/src/zoom/helpers.py +102 -0
  138. ingestr/tests/unit/test_smartsheets.py +133 -0
  139. ingestr-0.14.104.dist-info/METADATA +563 -0
  140. ingestr-0.14.104.dist-info/RECORD +203 -0
  141. ingestr/src/appsflyer/_init_.py +0 -24
  142. ingestr-0.13.2.dist-info/METADATA +0 -302
  143. ingestr-0.13.2.dist-info/RECORD +0 -107
  144. {ingestr-0.13.2.dist-info → ingestr-0.14.104.dist-info}/WHEEL +0 -0
  145. {ingestr-0.13.2.dist-info → ingestr-0.14.104.dist-info}/entry_points.txt +0 -0
  146. {ingestr-0.13.2.dist-info → ingestr-0.14.104.dist-info}/licenses/LICENSE.md +0 -0
@@ -0,0 +1,439 @@
1
+ """Jira source helpers"""
2
+
3
+ import base64
4
+ import logging
5
+ import time
6
+ from typing import Any, Dict, Iterator, Optional
7
+ from urllib.parse import urljoin
8
+
9
+ import requests
10
+
11
+ from .settings import API_BASE_PATH, DEFAULT_PAGE_SIZE, MAX_PAGE_SIZE, REQUEST_TIMEOUT
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class JiraAPIError(Exception):
17
+ """Custom exception for Jira API errors."""
18
+
19
+ def __init__(
20
+ self,
21
+ message: str,
22
+ status_code: Optional[int] = None,
23
+ response_text: Optional[str] = None,
24
+ ):
25
+ super().__init__(message)
26
+ self.status_code = status_code
27
+ self.response_text = response_text
28
+
29
+
30
+ class JiraAuthenticationError(JiraAPIError):
31
+ """Exception raised for authentication failures."""
32
+
33
+ pass
34
+
35
+
36
+ class JiraRateLimitError(JiraAPIError):
37
+ """Exception raised when rate limit is exceeded."""
38
+
39
+ pass
40
+
41
+
42
+ class JiraClient:
43
+ """Jira REST API client with authentication and pagination support."""
44
+
45
+ def __init__(
46
+ self, base_url: str, email: str, api_token: str, timeout: int = REQUEST_TIMEOUT
47
+ ):
48
+ """
49
+ Initialize Jira client with basic auth.
50
+
51
+ Args:
52
+ base_url: Jira instance URL (e.g., https://your-domain.atlassian.net)
53
+ email: User email for authentication
54
+ api_token: API token for authentication
55
+ timeout: Request timeout in seconds
56
+ """
57
+ self.base_url = base_url.rstrip("/")
58
+ self.api_url = urljoin(self.base_url, API_BASE_PATH)
59
+ self.timeout = timeout
60
+
61
+ # Create basic auth header
62
+ credentials = f"{email}:{api_token}"
63
+ encoded_credentials = base64.b64encode(credentials.encode()).decode()
64
+
65
+ self.headers = {
66
+ "Authorization": f"Basic {encoded_credentials}",
67
+ "Accept": "application/json",
68
+ "Content-Type": "application/json",
69
+ }
70
+
71
+ def _make_request(
72
+ self,
73
+ endpoint: str,
74
+ params: Optional[Dict[str, Any]] = None,
75
+ method: str = "GET",
76
+ max_retries: int = 3,
77
+ backoff_factor: float = 1.0,
78
+ ) -> Dict[str, Any]:
79
+ """
80
+ Make HTTP request to Jira API with retry logic.
81
+
82
+ Args:
83
+ endpoint: API endpoint path
84
+ params: Query parameters
85
+ method: HTTP method
86
+ max_retries: Maximum number of retry attempts
87
+ backoff_factor: Factor for exponential backoff
88
+
89
+ Returns:
90
+ JSON response data
91
+
92
+ Raises:
93
+ JiraAPIError: If request fails after retries
94
+ JiraAuthenticationError: If authentication fails
95
+ JiraRateLimitError: If rate limit is exceeded
96
+ """
97
+ url = urljoin(self.api_url + "/", endpoint.lstrip("/"))
98
+
99
+ for attempt in range(max_retries + 1):
100
+ try:
101
+ response = requests.request(
102
+ method=method,
103
+ url=url,
104
+ headers=self.headers,
105
+ params=params,
106
+ timeout=self.timeout,
107
+ )
108
+
109
+ # Handle different error status codes
110
+ if response.status_code == 401:
111
+ raise JiraAuthenticationError(
112
+ "Authentication failed. Please check your email and API token.",
113
+ status_code=response.status_code,
114
+ response_text=response.text,
115
+ )
116
+ elif response.status_code == 403:
117
+ raise JiraAuthenticationError(
118
+ "Access forbidden. Please check your permissions.",
119
+ status_code=response.status_code,
120
+ response_text=response.text,
121
+ )
122
+ elif response.status_code == 429:
123
+ # Rate limit exceeded
124
+ retry_after = int(response.headers.get("Retry-After", 60))
125
+ if attempt < max_retries:
126
+ logger.warning(
127
+ f"Rate limit exceeded. Waiting {retry_after} seconds before retry."
128
+ )
129
+ time.sleep(retry_after) # type: ignore
130
+ continue
131
+ else:
132
+ raise JiraRateLimitError(
133
+ f"Rate limit exceeded after {max_retries} retries.",
134
+ status_code=response.status_code,
135
+ response_text=response.text,
136
+ )
137
+ elif response.status_code >= 500:
138
+ # Server error - retry with backoff
139
+ if attempt < max_retries:
140
+ wait_time = backoff_factor * (2**attempt)
141
+ logger.warning(
142
+ f"Server error {response.status_code}. Retrying in {wait_time} seconds."
143
+ )
144
+ time.sleep(wait_time) # type: ignore
145
+ continue
146
+ else:
147
+ raise JiraAPIError(
148
+ f"Server error after {max_retries} retries.",
149
+ status_code=response.status_code,
150
+ response_text=response.text,
151
+ )
152
+
153
+ # Raise for other HTTP errors
154
+ response.raise_for_status()
155
+
156
+ # Try to parse JSON response
157
+ try:
158
+ return response.json()
159
+ except ValueError as e:
160
+ raise JiraAPIError(
161
+ f"Invalid JSON response: {str(e)}",
162
+ status_code=response.status_code,
163
+ response_text=response.text,
164
+ )
165
+
166
+ except requests.RequestException as e:
167
+ if attempt < max_retries:
168
+ wait_time = backoff_factor * (2**attempt)
169
+ logger.warning(
170
+ f"Request failed: {str(e)}. Retrying in {wait_time} seconds."
171
+ )
172
+ time.sleep(wait_time) # type: ignore
173
+ continue
174
+ else:
175
+ raise JiraAPIError(
176
+ f"Request failed after {max_retries} retries: {str(e)}"
177
+ )
178
+
179
+ raise JiraAPIError(f"Request failed after {max_retries} retries")
180
+
181
+ def get_paginated(
182
+ self,
183
+ endpoint: str,
184
+ params: Optional[Dict[str, Any]] = None,
185
+ page_size: int = DEFAULT_PAGE_SIZE,
186
+ max_results: Optional[int] = None,
187
+ ) -> Iterator[Dict[str, Any]]:
188
+ """
189
+ Get paginated results from Jira API with error handling.
190
+
191
+ Args:
192
+ endpoint: API endpoint path
193
+ params: Query parameters
194
+ page_size: Number of items per page
195
+ max_results: Maximum total results to return
196
+
197
+ Yields:
198
+ Individual items from paginated response
199
+
200
+ Raises:
201
+ JiraAPIError: If pagination fails
202
+ """
203
+ if params is None:
204
+ params = {}
205
+
206
+ # Validate page size
207
+ page_size = min(max(1, page_size), MAX_PAGE_SIZE)
208
+ params["maxResults"] = page_size
209
+ params["startAt"] = 0
210
+
211
+ total_returned = 0
212
+ consecutive_empty_pages = 0
213
+ max_empty_pages = 3
214
+
215
+ while True:
216
+ try:
217
+ response = self._make_request(endpoint, params)
218
+
219
+ # Handle different response structures
220
+ if "values" in response:
221
+ items = response["values"]
222
+ total = response.get("total", len(items))
223
+ is_last = response.get("isLast", False)
224
+ elif "issues" in response:
225
+ items = response["issues"]
226
+ total = response.get("total", len(items))
227
+ is_last = len(items) < page_size
228
+ elif isinstance(response, list):
229
+ # Some endpoints return arrays directly
230
+ items = response
231
+ total = len(items)
232
+ is_last = True
233
+ else:
234
+ # Single item response
235
+ yield response
236
+ break
237
+
238
+ # Check for empty pages
239
+ if not items:
240
+ consecutive_empty_pages += 1
241
+ if consecutive_empty_pages >= max_empty_pages:
242
+ logger.warning(
243
+ f"Received {consecutive_empty_pages} consecutive empty pages, stopping pagination"
244
+ )
245
+ break
246
+ else:
247
+ consecutive_empty_pages = 0
248
+
249
+ for item in items:
250
+ if max_results and total_returned >= max_results:
251
+ return
252
+ yield item
253
+ total_returned += 1
254
+
255
+ # Check if we've reached the end
256
+ if is_last or len(items) < page_size:
257
+ break
258
+
259
+ # Check if we've got all available items
260
+ if total and total_returned >= total:
261
+ break
262
+
263
+ # Move to next page
264
+ params["startAt"] += page_size
265
+
266
+ # Safety check to prevent infinite loops
267
+ if params["startAt"] > 100000: # Arbitrary large number
268
+ logger.warning(
269
+ f"Pagination safety limit reached for {endpoint}, stopping"
270
+ )
271
+ break
272
+
273
+ except JiraAPIError as e:
274
+ logger.error(f"API error during pagination of {endpoint}: {str(e)}")
275
+ raise
276
+ except Exception as e:
277
+ logger.error(
278
+ f"Unexpected error during pagination of {endpoint}: {str(e)}"
279
+ )
280
+ raise JiraAPIError(f"Pagination failed: {str(e)}")
281
+
282
+ def search_issues(
283
+ self,
284
+ jql: str,
285
+ fields: Optional[str] = None,
286
+ expand: Optional[str] = None,
287
+ page_size: int = DEFAULT_PAGE_SIZE,
288
+ max_results: Optional[int] = None,
289
+ ) -> Iterator[Dict[str, Any]]:
290
+ """
291
+ Search for issues using JQL.
292
+
293
+ Args:
294
+ jql: JQL query string
295
+ fields: Comma-separated list of fields to return
296
+ expand: Comma-separated list of fields to expand
297
+ page_size: Number of items per page
298
+ max_results: Maximum total results to return
299
+
300
+ Yields:
301
+ Issue data
302
+ """
303
+ params = {"jql": jql}
304
+ if fields:
305
+ params["fields"] = fields
306
+ if expand:
307
+ params["expand"] = expand
308
+
309
+ yield from self.get_paginated(
310
+ "search/jql", params=params, page_size=page_size, max_results=max_results
311
+ )
312
+
313
+ def get_projects(
314
+ self, expand: Optional[str] = None, recent: Optional[int] = None
315
+ ) -> Iterator[Dict[str, Any]]:
316
+ """
317
+ Get all projects.
318
+
319
+ Args:
320
+ expand: Comma-separated list of fields to expand
321
+ recent: Number of recent projects to return
322
+
323
+ Yields:
324
+ Project data
325
+ """
326
+ params = {}
327
+ if expand:
328
+ params["expand"] = expand
329
+ if recent:
330
+ params["recent"] = str(recent)
331
+
332
+ yield from self.get_paginated("project", params=params)
333
+
334
+ def get_users(
335
+ self,
336
+ username: Optional[str] = None,
337
+ account_id: Optional[str] = None,
338
+ start_at: int = 0,
339
+ max_results: int = DEFAULT_PAGE_SIZE,
340
+ ) -> Iterator[Dict[str, Any]]:
341
+ """
342
+ Get users.
343
+
344
+ Args:
345
+ username: Username to search for
346
+ account_id: Account ID to search for
347
+ start_at: Starting index
348
+ max_results: Maximum results per page
349
+
350
+ Yields:
351
+ User data
352
+ """
353
+ params = {
354
+ "startAt": str(start_at),
355
+ "maxResults": str(min(max_results, MAX_PAGE_SIZE)),
356
+ }
357
+ if username:
358
+ params["username"] = username
359
+ if account_id:
360
+ params["accountId"] = account_id
361
+
362
+ yield from self.get_paginated("users/search", params=params)
363
+
364
+ def get_issue_types(self) -> Iterator[Dict[str, Any]]:
365
+ """Get all issue types."""
366
+ response = self._make_request("issuetype")
367
+ if isinstance(response, list):
368
+ for issue_type in response:
369
+ yield issue_type
370
+
371
+ def get_statuses(self) -> Iterator[Dict[str, Any]]:
372
+ """Get all statuses."""
373
+ response = self._make_request("status")
374
+ if isinstance(response, list):
375
+ for status in response:
376
+ yield status
377
+
378
+ def get_priorities(self) -> Iterator[Dict[str, Any]]:
379
+ """Get all priorities."""
380
+ response = self._make_request("priority")
381
+ if isinstance(response, list):
382
+ for priority in response:
383
+ yield priority
384
+
385
+ def get_resolutions(self) -> Iterator[Dict[str, Any]]:
386
+ """Get all resolutions."""
387
+ response = self._make_request("resolution")
388
+ if isinstance(response, list):
389
+ for resolution in response:
390
+ yield resolution
391
+
392
+ def get_project_versions(self, project_key: str) -> Iterator[Dict[str, Any]]:
393
+ """
394
+ Get versions for a specific project.
395
+
396
+ Args:
397
+ project_key: Project key
398
+
399
+ Yields:
400
+ Version data
401
+ """
402
+ yield from self.get_paginated(f"project/{project_key}/version")
403
+
404
+ def get_project_components(self, project_key: str) -> Iterator[Dict[str, Any]]:
405
+ """
406
+ Get components for a specific project.
407
+
408
+ Args:
409
+ project_key: Project key
410
+
411
+ Yields:
412
+ Component data
413
+ """
414
+ yield from self.get_paginated(f"project/{project_key}/component")
415
+
416
+ def get_events(self) -> Iterator[Dict[str, Any]]:
417
+ """Get all events (issue events like created, updated, etc.)."""
418
+ response = self._make_request("events")
419
+ if isinstance(response, list):
420
+ for event in response:
421
+ yield event
422
+
423
+
424
+ def get_client(
425
+ base_url: str, email: str, api_token: str, timeout: int = REQUEST_TIMEOUT
426
+ ) -> JiraClient:
427
+ """
428
+ Create and return a Jira API client.
429
+
430
+ Args:
431
+ base_url: Jira instance URL
432
+ email: User email for authentication
433
+ api_token: API token for authentication
434
+ timeout: Request timeout in seconds
435
+
436
+ Returns:
437
+ JiraClient instance
438
+ """
439
+ return JiraClient(base_url, email, api_token, timeout)
@@ -0,0 +1,170 @@
1
+ """Jira source settings and constants"""
2
+
3
+ # Default start date for Jira API requests
4
+ DEFAULT_START_DATE = "2010-01-01"
5
+
6
+ # Jira API request timeout in seconds
7
+ REQUEST_TIMEOUT = 300
8
+
9
+ # Default page size for paginated requests
10
+ DEFAULT_PAGE_SIZE = 100
11
+
12
+ # Maximum page size allowed by Jira API
13
+ MAX_PAGE_SIZE = 1000
14
+
15
+ # Base API path for Jira Cloud
16
+ API_BASE_PATH = "/rest/api/3"
17
+
18
+ # Project fields to retrieve from Jira API
19
+ PROJECT_FIELDS = (
20
+ "id",
21
+ "key",
22
+ "name",
23
+ "description",
24
+ "lead",
25
+ "projectCategory",
26
+ "projectTypeKey",
27
+ "simplified",
28
+ "style",
29
+ "favourite",
30
+ "isPrivate",
31
+ "properties",
32
+ "entityId",
33
+ "uuid",
34
+ "insight",
35
+ )
36
+
37
+ # Issue fields to retrieve from Jira API
38
+ ISSUE_FIELDS = (
39
+ "id",
40
+ "key",
41
+ "summary",
42
+ "description",
43
+ "issuetype",
44
+ "status",
45
+ "priority",
46
+ "resolution",
47
+ "assignee",
48
+ "reporter",
49
+ "creator",
50
+ "created",
51
+ "updated",
52
+ "resolutiondate",
53
+ "duedate",
54
+ "components",
55
+ "fixVersions",
56
+ "versions",
57
+ "labels",
58
+ "environment",
59
+ "project",
60
+ "parent",
61
+ "subtasks",
62
+ "issuelinks",
63
+ "votes",
64
+ "watches",
65
+ "worklog",
66
+ "attachments",
67
+ "comment",
68
+ "customfield_*",
69
+ )
70
+
71
+ # User fields to retrieve from Jira API
72
+ USER_FIELDS = (
73
+ "accountId",
74
+ "accountType",
75
+ "emailAddress",
76
+ "displayName",
77
+ "active",
78
+ "timeZone",
79
+ "groups",
80
+ "applicationRoles",
81
+ "expand",
82
+ )
83
+
84
+ # Board fields to retrieve from Jira API (for Agile/Scrum boards)
85
+ BOARD_FIELDS = (
86
+ "id",
87
+ "name",
88
+ "type",
89
+ "location",
90
+ "filter",
91
+ "subQuery",
92
+ )
93
+
94
+ # Sprint fields to retrieve from Jira API
95
+ SPRINT_FIELDS = (
96
+ "id",
97
+ "name",
98
+ "state",
99
+ "startDate",
100
+ "endDate",
101
+ "completeDate",
102
+ "originBoardId",
103
+ "goal",
104
+ )
105
+
106
+ # Issue type fields to retrieve from Jira API
107
+ ISSUE_TYPE_FIELDS = (
108
+ "id",
109
+ "name",
110
+ "description",
111
+ "iconUrl",
112
+ "subtask",
113
+ "avatarId",
114
+ "hierarchyLevel",
115
+ )
116
+
117
+ # Status fields to retrieve from Jira API
118
+ STATUS_FIELDS = (
119
+ "id",
120
+ "name",
121
+ "description",
122
+ "iconUrl",
123
+ "statusCategory",
124
+ )
125
+
126
+ # Priority fields to retrieve from Jira API
127
+ PRIORITY_FIELDS = (
128
+ "id",
129
+ "name",
130
+ "description",
131
+ "iconUrl",
132
+ )
133
+
134
+ # Resolution fields to retrieve from Jira API
135
+ RESOLUTION_FIELDS = (
136
+ "id",
137
+ "name",
138
+ "description",
139
+ )
140
+
141
+ # Version fields to retrieve from Jira API
142
+ VERSION_FIELDS = (
143
+ "id",
144
+ "name",
145
+ "description",
146
+ "archived",
147
+ "released",
148
+ "startDate",
149
+ "releaseDate",
150
+ "overdue",
151
+ "userStartDate",
152
+ "userReleaseDate",
153
+ "project",
154
+ "projectId",
155
+ )
156
+
157
+ # Component fields to retrieve from Jira API
158
+ COMPONENT_FIELDS = (
159
+ "id",
160
+ "name",
161
+ "description",
162
+ "lead",
163
+ "assigneeType",
164
+ "assignee",
165
+ "realAssigneeType",
166
+ "realAssignee",
167
+ "isAssigneeTypeValid",
168
+ "project",
169
+ "projectId",
170
+ )
@@ -83,7 +83,7 @@ def kafka_consumer(
83
83
  # read messages up to the maximum offsets,
84
84
  # not waiting for new messages
85
85
  with closing(consumer):
86
- while tracker.has_unread:
86
+ while True:
87
87
  messages = consumer.consume(batch_size, timeout=batch_timeout)
88
88
  if not messages:
89
89
  break
@@ -101,3 +101,6 @@ def kafka_consumer(
101
101
  tracker.renew(msg)
102
102
 
103
103
  yield batch
104
+
105
+ if tracker.has_unread is False:
106
+ return