ingestr 0.13.75__py3-none-any.whl → 0.14.98__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.
Potentially problematic release.
This version of ingestr might be problematic. Click here for more details.
- ingestr/main.py +22 -3
- ingestr/src/adjust/__init__.py +4 -4
- ingestr/src/allium/__init__.py +128 -0
- ingestr/src/anthropic/__init__.py +277 -0
- ingestr/src/anthropic/helpers.py +525 -0
- ingestr/src/appstore/__init__.py +1 -0
- ingestr/src/asana_source/__init__.py +1 -1
- ingestr/src/buildinfo.py +1 -1
- ingestr/src/chess/__init__.py +1 -1
- ingestr/src/couchbase_source/__init__.py +118 -0
- ingestr/src/couchbase_source/helpers.py +135 -0
- ingestr/src/cursor/__init__.py +83 -0
- ingestr/src/cursor/helpers.py +188 -0
- ingestr/src/destinations.py +169 -1
- ingestr/src/docebo/__init__.py +589 -0
- ingestr/src/docebo/client.py +435 -0
- ingestr/src/docebo/helpers.py +97 -0
- ingestr/src/elasticsearch/helpers.py +138 -0
- ingestr/src/errors.py +8 -0
- ingestr/src/facebook_ads/__init__.py +26 -23
- ingestr/src/facebook_ads/helpers.py +47 -1
- ingestr/src/factory.py +48 -0
- ingestr/src/filesystem/__init__.py +8 -3
- ingestr/src/filters.py +9 -0
- ingestr/src/fluxx/__init__.py +9906 -0
- ingestr/src/fluxx/helpers.py +209 -0
- ingestr/src/frankfurter/__init__.py +157 -163
- ingestr/src/frankfurter/helpers.py +3 -3
- ingestr/src/freshdesk/__init__.py +25 -8
- ingestr/src/freshdesk/freshdesk_client.py +40 -5
- ingestr/src/fundraiseup/__init__.py +49 -0
- ingestr/src/fundraiseup/client.py +81 -0
- ingestr/src/github/__init__.py +6 -4
- ingestr/src/google_analytics/__init__.py +1 -1
- ingestr/src/hostaway/__init__.py +302 -0
- ingestr/src/hostaway/client.py +288 -0
- ingestr/src/http/__init__.py +35 -0
- ingestr/src/http/readers.py +114 -0
- ingestr/src/hubspot/__init__.py +6 -12
- ingestr/src/influxdb/__init__.py +1 -0
- ingestr/src/intercom/__init__.py +142 -0
- ingestr/src/intercom/helpers.py +674 -0
- ingestr/src/intercom/settings.py +279 -0
- ingestr/src/jira_source/__init__.py +340 -0
- ingestr/src/jira_source/helpers.py +439 -0
- ingestr/src/jira_source/settings.py +170 -0
- ingestr/src/klaviyo/__init__.py +5 -5
- ingestr/src/linear/__init__.py +553 -116
- ingestr/src/linear/helpers.py +77 -38
- ingestr/src/mailchimp/__init__.py +126 -0
- ingestr/src/mailchimp/helpers.py +226 -0
- ingestr/src/mailchimp/settings.py +164 -0
- ingestr/src/masking.py +344 -0
- ingestr/src/monday/__init__.py +246 -0
- ingestr/src/monday/helpers.py +392 -0
- ingestr/src/monday/settings.py +328 -0
- ingestr/src/mongodb/__init__.py +5 -2
- ingestr/src/mongodb/helpers.py +384 -10
- ingestr/src/plusvibeai/__init__.py +335 -0
- ingestr/src/plusvibeai/helpers.py +544 -0
- ingestr/src/plusvibeai/settings.py +252 -0
- ingestr/src/revenuecat/__init__.py +83 -0
- ingestr/src/revenuecat/helpers.py +237 -0
- ingestr/src/salesforce/__init__.py +15 -8
- ingestr/src/shopify/__init__.py +1 -1
- ingestr/src/smartsheets/__init__.py +33 -5
- ingestr/src/socrata_source/__init__.py +83 -0
- ingestr/src/socrata_source/helpers.py +85 -0
- ingestr/src/socrata_source/settings.py +8 -0
- ingestr/src/sources.py +1418 -54
- ingestr/src/stripe_analytics/__init__.py +2 -19
- ingestr/src/wise/__init__.py +68 -0
- ingestr/src/wise/client.py +63 -0
- ingestr/tests/unit/test_smartsheets.py +6 -9
- {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/METADATA +24 -12
- {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/RECORD +79 -37
- {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/WHEEL +0 -0
- {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/entry_points.txt +0 -0
- {ingestr-0.13.75.dist-info → ingestr-0.14.98.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration settings and constants for Intercom API integration.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Dict, List, Tuple
|
|
7
|
+
|
|
8
|
+
# API Version - REQUIRED for all requests
|
|
9
|
+
API_VERSION = "2.14"
|
|
10
|
+
|
|
11
|
+
# Default start date for incremental loading
|
|
12
|
+
DEFAULT_START_DATE = datetime(2020, 1, 1)
|
|
13
|
+
|
|
14
|
+
# Pagination settings
|
|
15
|
+
DEFAULT_PAGE_SIZE = 150
|
|
16
|
+
MAX_PAGE_SIZE = 150 # Intercom's maximum
|
|
17
|
+
SCROLL_EXPIRY_SECONDS = 60 # Scroll sessions expire after 1 minute
|
|
18
|
+
|
|
19
|
+
# Rate limiting settings
|
|
20
|
+
RATE_LIMIT_PER_10_SECONDS = 166
|
|
21
|
+
RATE_LIMIT_RETRY_AFTER_DEFAULT = 10
|
|
22
|
+
|
|
23
|
+
# Regional API endpoints
|
|
24
|
+
REGIONAL_ENDPOINTS = {
|
|
25
|
+
"us": "https://api.intercom.io",
|
|
26
|
+
"eu": "https://api.eu.intercom.io",
|
|
27
|
+
"au": "https://api.au.intercom.io",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
# Resource configuration for automatic generation
|
|
31
|
+
# Format: resource_name -> config dict
|
|
32
|
+
RESOURCE_CONFIGS = {
|
|
33
|
+
# Search-based incremental resources
|
|
34
|
+
"contacts": {
|
|
35
|
+
"type": "search",
|
|
36
|
+
"incremental": True,
|
|
37
|
+
"transform_func": "transform_contact",
|
|
38
|
+
"columns": {
|
|
39
|
+
"custom_attributes": {"data_type": "json"},
|
|
40
|
+
"tags": {"data_type": "json"},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
"conversations": {
|
|
44
|
+
"type": "search",
|
|
45
|
+
"incremental": True,
|
|
46
|
+
"transform_func": "transform_conversation",
|
|
47
|
+
"columns": {
|
|
48
|
+
"custom_attributes": {"data_type": "json"},
|
|
49
|
+
"tags": {"data_type": "json"},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
# Pagination-based incremental resources
|
|
53
|
+
"companies": {
|
|
54
|
+
"type": "pagination",
|
|
55
|
+
"endpoint": "/companies",
|
|
56
|
+
"data_key": "data",
|
|
57
|
+
"pagination_type": "cursor",
|
|
58
|
+
"incremental": True,
|
|
59
|
+
"transform_func": "transform_company",
|
|
60
|
+
"params": {"per_page": 50},
|
|
61
|
+
"columns": {
|
|
62
|
+
"custom_attributes": {"data_type": "json"},
|
|
63
|
+
"tags": {"data_type": "json"},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
"articles": {
|
|
67
|
+
"type": "pagination",
|
|
68
|
+
"endpoint": "/articles",
|
|
69
|
+
"data_key": "data",
|
|
70
|
+
"pagination_type": "cursor",
|
|
71
|
+
"incremental": True,
|
|
72
|
+
"transform_func": None,
|
|
73
|
+
"params": None,
|
|
74
|
+
"columns": {},
|
|
75
|
+
},
|
|
76
|
+
# Special case - tickets
|
|
77
|
+
"tickets": {
|
|
78
|
+
"type": "tickets",
|
|
79
|
+
"incremental": True,
|
|
80
|
+
"transform_func": None,
|
|
81
|
+
"columns": {
|
|
82
|
+
"ticket_attributes": {"data_type": "json"},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
# Simple replace resources (non-incremental)
|
|
86
|
+
"tags": {
|
|
87
|
+
"type": "simple",
|
|
88
|
+
"endpoint": "/tags",
|
|
89
|
+
"data_key": "data",
|
|
90
|
+
"pagination_type": "simple",
|
|
91
|
+
"incremental": False,
|
|
92
|
+
"transform_func": None,
|
|
93
|
+
"columns": {},
|
|
94
|
+
},
|
|
95
|
+
"segments": {
|
|
96
|
+
"type": "simple",
|
|
97
|
+
"endpoint": "/segments",
|
|
98
|
+
"data_key": "segments",
|
|
99
|
+
"pagination_type": "cursor",
|
|
100
|
+
"incremental": False,
|
|
101
|
+
"transform_func": None,
|
|
102
|
+
"columns": {},
|
|
103
|
+
},
|
|
104
|
+
"teams": {
|
|
105
|
+
"type": "simple",
|
|
106
|
+
"endpoint": "/teams",
|
|
107
|
+
"data_key": "teams",
|
|
108
|
+
"pagination_type": "simple",
|
|
109
|
+
"incremental": False,
|
|
110
|
+
"transform_func": None,
|
|
111
|
+
"columns": {},
|
|
112
|
+
},
|
|
113
|
+
"admins": {
|
|
114
|
+
"type": "simple",
|
|
115
|
+
"endpoint": "/admins",
|
|
116
|
+
"data_key": "admins",
|
|
117
|
+
"pagination_type": "simple",
|
|
118
|
+
"incremental": False,
|
|
119
|
+
"transform_func": None,
|
|
120
|
+
"columns": {},
|
|
121
|
+
},
|
|
122
|
+
"data_attributes": {
|
|
123
|
+
"type": "simple",
|
|
124
|
+
"endpoint": "/data_attributes",
|
|
125
|
+
"data_key": "data",
|
|
126
|
+
"pagination_type": "cursor",
|
|
127
|
+
"incremental": False,
|
|
128
|
+
"transform_func": None,
|
|
129
|
+
"columns": {
|
|
130
|
+
"id": {"data_type": "bigint", "nullable": True},
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Core endpoints with their configuration (kept for backwards compatibility)
|
|
136
|
+
# Format: (endpoint_path, data_key, supports_incremental, pagination_type)
|
|
137
|
+
CORE_ENDPOINTS: Dict[str, Tuple[str, str, bool, str]] = {
|
|
138
|
+
"contacts": ("/contacts", "data", True, "cursor"),
|
|
139
|
+
"companies": ("/companies", "data", True, "cursor"),
|
|
140
|
+
"conversations": ("/conversations", "conversations", True, "cursor"),
|
|
141
|
+
"tickets": ("/tickets", "tickets", True, "cursor"),
|
|
142
|
+
"admins": ("/admins", "admins", False, "simple"),
|
|
143
|
+
"teams": ("/teams", "teams", False, "simple"),
|
|
144
|
+
"tags": ("/tags", "data", False, "simple"),
|
|
145
|
+
"segments": ("/segments", "segments", False, "cursor"),
|
|
146
|
+
"articles": ("/articles", "data", True, "cursor"),
|
|
147
|
+
"collections": ("/help_center/collections", "data", False, "cursor"),
|
|
148
|
+
"data_attributes": ("/data_attributes", "data", False, "cursor"),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# Incremental endpoints using search API
|
|
152
|
+
SEARCH_ENDPOINTS: Dict[str, str] = {
|
|
153
|
+
"contacts_search": "/contacts/search",
|
|
154
|
+
"companies_search": "/companies/search",
|
|
155
|
+
"conversations_search": "/conversations/search",
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
# Special endpoints requiring different handling
|
|
159
|
+
SCROLL_ENDPOINTS: List[str] = [
|
|
160
|
+
"companies", # Can use scroll for large exports
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
# Event tracking endpoint
|
|
164
|
+
EVENTS_ENDPOINT = "/events"
|
|
165
|
+
|
|
166
|
+
# Ticket fields endpoint for custom field mapping
|
|
167
|
+
TICKET_FIELDS_ENDPOINT = "/ticket_types/{ticket_type_id}/attributes"
|
|
168
|
+
|
|
169
|
+
# Default fields to retrieve for each resource type
|
|
170
|
+
DEFAULT_CONTACT_FIELDS = [
|
|
171
|
+
"id",
|
|
172
|
+
"type",
|
|
173
|
+
"external_id",
|
|
174
|
+
"email",
|
|
175
|
+
"phone",
|
|
176
|
+
"name",
|
|
177
|
+
"created_at",
|
|
178
|
+
"updated_at",
|
|
179
|
+
"signed_up_at",
|
|
180
|
+
"last_seen_at",
|
|
181
|
+
"last_contacted_at",
|
|
182
|
+
"last_email_opened_at",
|
|
183
|
+
"last_email_clicked_at",
|
|
184
|
+
"browser",
|
|
185
|
+
"browser_language",
|
|
186
|
+
"browser_version",
|
|
187
|
+
"location",
|
|
188
|
+
"os",
|
|
189
|
+
"role",
|
|
190
|
+
"custom_attributes",
|
|
191
|
+
"tags",
|
|
192
|
+
"companies",
|
|
193
|
+
]
|
|
194
|
+
|
|
195
|
+
DEFAULT_COMPANY_FIELDS = [
|
|
196
|
+
"id",
|
|
197
|
+
"type",
|
|
198
|
+
"company_id",
|
|
199
|
+
"name",
|
|
200
|
+
"plan",
|
|
201
|
+
"size",
|
|
202
|
+
"website",
|
|
203
|
+
"industry",
|
|
204
|
+
"created_at",
|
|
205
|
+
"updated_at",
|
|
206
|
+
"monthly_spend",
|
|
207
|
+
"session_count",
|
|
208
|
+
"user_count",
|
|
209
|
+
"custom_attributes",
|
|
210
|
+
"tags",
|
|
211
|
+
]
|
|
212
|
+
|
|
213
|
+
DEFAULT_CONVERSATION_FIELDS = [
|
|
214
|
+
"id",
|
|
215
|
+
"type",
|
|
216
|
+
"created_at",
|
|
217
|
+
"updated_at",
|
|
218
|
+
"waiting_since",
|
|
219
|
+
"snoozed_until",
|
|
220
|
+
"state",
|
|
221
|
+
"open",
|
|
222
|
+
"read",
|
|
223
|
+
"priority",
|
|
224
|
+
"admin_assignee_id",
|
|
225
|
+
"team_assignee_id",
|
|
226
|
+
"tags",
|
|
227
|
+
"conversation_rating",
|
|
228
|
+
"source",
|
|
229
|
+
"contacts",
|
|
230
|
+
"teammates",
|
|
231
|
+
"custom_attributes",
|
|
232
|
+
"first_contact_reply",
|
|
233
|
+
"sla_applied",
|
|
234
|
+
"statistics",
|
|
235
|
+
"conversation_parts",
|
|
236
|
+
]
|
|
237
|
+
|
|
238
|
+
DEFAULT_TICKET_FIELDS = [
|
|
239
|
+
"id",
|
|
240
|
+
"type",
|
|
241
|
+
"ticket_id",
|
|
242
|
+
"category",
|
|
243
|
+
"ticket_attributes",
|
|
244
|
+
"ticket_state",
|
|
245
|
+
"ticket_type",
|
|
246
|
+
"created_at",
|
|
247
|
+
"updated_at",
|
|
248
|
+
"ticket_parts",
|
|
249
|
+
"contacts",
|
|
250
|
+
"admin_assignee_id",
|
|
251
|
+
"team_assignee_id",
|
|
252
|
+
"open",
|
|
253
|
+
"snoozed_until",
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
# Resources that support custom attributes
|
|
257
|
+
SUPPORTS_CUSTOM_ATTRIBUTES = [
|
|
258
|
+
"contacts",
|
|
259
|
+
"companies",
|
|
260
|
+
"conversations",
|
|
261
|
+
]
|
|
262
|
+
|
|
263
|
+
# Maximum limits
|
|
264
|
+
MAX_CUSTOM_ATTRIBUTES_PER_RESOURCE = 100
|
|
265
|
+
MAX_EVENT_TYPES_PER_WORKSPACE = 120
|
|
266
|
+
MAX_CONVERSATION_PARTS = 500
|
|
267
|
+
MAX_SEARCH_RESULTS = 10000
|
|
268
|
+
|
|
269
|
+
# Field type mapping for custom attributes
|
|
270
|
+
INTERCOM_TO_DLT_TYPE_MAPPING = {
|
|
271
|
+
"string": "text",
|
|
272
|
+
"integer": "bigint",
|
|
273
|
+
"float": "double",
|
|
274
|
+
"boolean": "bool",
|
|
275
|
+
"date": "timestamp",
|
|
276
|
+
"datetime": "timestamp",
|
|
277
|
+
"object": "json",
|
|
278
|
+
"list": "json",
|
|
279
|
+
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This source provides data extraction from Jira Cloud via the REST API v3.
|
|
3
|
+
|
|
4
|
+
It defines several functions to fetch data from different parts of Jira including
|
|
5
|
+
projects, issues, users, boards, sprints, and various configuration objects like
|
|
6
|
+
issue types, statuses, and priorities.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any, Iterable, Optional
|
|
10
|
+
|
|
11
|
+
import dlt
|
|
12
|
+
from dlt.common.typing import TDataItem
|
|
13
|
+
|
|
14
|
+
from .helpers import get_client
|
|
15
|
+
from .settings import (
|
|
16
|
+
DEFAULT_PAGE_SIZE,
|
|
17
|
+
DEFAULT_START_DATE,
|
|
18
|
+
ISSUE_FIELDS,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dlt.source
|
|
23
|
+
def jira_source() -> Any:
|
|
24
|
+
"""
|
|
25
|
+
The main function that runs all the other functions to fetch data from Jira.
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Sequence[DltResource]: A sequence of DltResource objects containing the fetched data.
|
|
29
|
+
"""
|
|
30
|
+
return [
|
|
31
|
+
projects,
|
|
32
|
+
issues,
|
|
33
|
+
users,
|
|
34
|
+
issue_types,
|
|
35
|
+
statuses,
|
|
36
|
+
priorities,
|
|
37
|
+
resolutions,
|
|
38
|
+
project_versions,
|
|
39
|
+
project_components,
|
|
40
|
+
events,
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dlt.resource(write_disposition="replace")
|
|
45
|
+
def projects(
|
|
46
|
+
base_url: str = dlt.secrets.value,
|
|
47
|
+
email: str = dlt.secrets.value,
|
|
48
|
+
api_token: str = dlt.secrets.value,
|
|
49
|
+
expand: Optional[str] = None,
|
|
50
|
+
recent: Optional[int] = None,
|
|
51
|
+
) -> Iterable[TDataItem]:
|
|
52
|
+
"""
|
|
53
|
+
Fetches and returns a list of projects from Jira.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
base_url (str): Jira instance URL (e.g., https://your-domain.atlassian.net)
|
|
57
|
+
email (str): User email for authentication
|
|
58
|
+
api_token (str): API token for authentication
|
|
59
|
+
expand (str): Comma-separated list of fields to expand
|
|
60
|
+
recent (int): Number of recent projects to return
|
|
61
|
+
|
|
62
|
+
Yields:
|
|
63
|
+
dict: The project data.
|
|
64
|
+
"""
|
|
65
|
+
client = get_client(base_url, email, api_token)
|
|
66
|
+
yield from client.get_projects(expand=expand, recent=recent)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dlt.resource(
|
|
70
|
+
write_disposition="merge",
|
|
71
|
+
primary_key="id",
|
|
72
|
+
max_table_nesting=2,
|
|
73
|
+
)
|
|
74
|
+
def issues(
|
|
75
|
+
base_url: str = dlt.secrets.value,
|
|
76
|
+
email: str = dlt.secrets.value,
|
|
77
|
+
api_token: str = dlt.secrets.value,
|
|
78
|
+
jql: str = "order by updated DESC",
|
|
79
|
+
fields: Optional[str] = None,
|
|
80
|
+
expand: Optional[str] = None,
|
|
81
|
+
max_results: Optional[int] = None,
|
|
82
|
+
updated: dlt.sources.incremental[str] = dlt.sources.incremental(
|
|
83
|
+
"fields.updated",
|
|
84
|
+
initial_value=DEFAULT_START_DATE,
|
|
85
|
+
range_end="closed",
|
|
86
|
+
range_start="closed",
|
|
87
|
+
),
|
|
88
|
+
) -> Iterable[TDataItem]:
|
|
89
|
+
"""
|
|
90
|
+
Fetches issues from Jira using JQL search.
|
|
91
|
+
|
|
92
|
+
Args:
|
|
93
|
+
base_url (str): Jira instance URL
|
|
94
|
+
email (str): User email for authentication
|
|
95
|
+
api_token (str): API token for authentication
|
|
96
|
+
jql (str): JQL query string
|
|
97
|
+
fields (str): Comma-separated list of fields to return
|
|
98
|
+
expand (str): Comma-separated list of fields to expand
|
|
99
|
+
max_results (int): Maximum number of results to return
|
|
100
|
+
updated (str): The date from which to fetch updated issues
|
|
101
|
+
|
|
102
|
+
Yields:
|
|
103
|
+
dict: The issue data.
|
|
104
|
+
"""
|
|
105
|
+
client = get_client(base_url, email, api_token)
|
|
106
|
+
|
|
107
|
+
# Build JQL with incremental filter
|
|
108
|
+
incremental_jql = jql
|
|
109
|
+
if updated.start_value:
|
|
110
|
+
date_filter = f"updated >= '{updated.start_value}'"
|
|
111
|
+
|
|
112
|
+
# Check if JQL has ORDER BY clause and handle it properly
|
|
113
|
+
jql_upper = jql.upper()
|
|
114
|
+
if "ORDER BY" in jql_upper:
|
|
115
|
+
# Split at ORDER BY and add filter before it
|
|
116
|
+
order_by_index = jql_upper.find("ORDER BY")
|
|
117
|
+
main_query = jql[:order_by_index].strip()
|
|
118
|
+
order_clause = jql[order_by_index:].strip()
|
|
119
|
+
|
|
120
|
+
if main_query and (
|
|
121
|
+
"WHERE" in main_query.upper()
|
|
122
|
+
or "AND" in main_query.upper()
|
|
123
|
+
or "OR" in main_query.upper()
|
|
124
|
+
):
|
|
125
|
+
incremental_jql = f"({main_query}) AND {date_filter} {order_clause}"
|
|
126
|
+
else:
|
|
127
|
+
if main_query:
|
|
128
|
+
incremental_jql = f"{main_query} AND {date_filter} {order_clause}"
|
|
129
|
+
else:
|
|
130
|
+
incremental_jql = f"{date_filter} {order_clause}"
|
|
131
|
+
else:
|
|
132
|
+
# No ORDER BY clause, use original logic
|
|
133
|
+
if "WHERE" in jql_upper or "AND" in jql_upper or "OR" in jql_upper:
|
|
134
|
+
incremental_jql = f"({jql}) AND {date_filter}"
|
|
135
|
+
else:
|
|
136
|
+
incremental_jql = f"{jql} AND {date_filter}"
|
|
137
|
+
|
|
138
|
+
# Use default fields if not specified
|
|
139
|
+
if fields is None:
|
|
140
|
+
fields = ",".join(ISSUE_FIELDS)
|
|
141
|
+
|
|
142
|
+
yield from client.search_issues(
|
|
143
|
+
jql=incremental_jql, fields=fields, expand=expand, max_results=max_results
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dlt.resource(write_disposition="replace")
|
|
148
|
+
def users(
|
|
149
|
+
base_url: str = dlt.secrets.value,
|
|
150
|
+
email: str = dlt.secrets.value,
|
|
151
|
+
api_token: str = dlt.secrets.value,
|
|
152
|
+
username: Optional[str] = None,
|
|
153
|
+
account_id: Optional[str] = None,
|
|
154
|
+
max_results: int = DEFAULT_PAGE_SIZE,
|
|
155
|
+
) -> Iterable[TDataItem]:
|
|
156
|
+
"""
|
|
157
|
+
Fetches users from Jira.
|
|
158
|
+
|
|
159
|
+
Args:
|
|
160
|
+
base_url (str): Jira instance URL
|
|
161
|
+
email (str): User email for authentication
|
|
162
|
+
api_token (str): API token for authentication
|
|
163
|
+
username (str): Username to search for
|
|
164
|
+
account_id (str): Account ID to search for
|
|
165
|
+
max_results (int): Maximum results per page
|
|
166
|
+
|
|
167
|
+
Yields:
|
|
168
|
+
dict: The user data.
|
|
169
|
+
"""
|
|
170
|
+
client = get_client(base_url, email, api_token)
|
|
171
|
+
yield from client.get_users(
|
|
172
|
+
username=username, account_id=account_id, max_results=max_results
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dlt.resource(write_disposition="replace")
|
|
177
|
+
def issue_types(
|
|
178
|
+
base_url: str = dlt.secrets.value,
|
|
179
|
+
email: str = dlt.secrets.value,
|
|
180
|
+
api_token: str = dlt.secrets.value,
|
|
181
|
+
) -> Iterable[TDataItem]:
|
|
182
|
+
"""
|
|
183
|
+
Fetches all issue types from Jira.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
base_url (str): Jira instance URL
|
|
187
|
+
email (str): User email for authentication
|
|
188
|
+
api_token (str): API token for authentication
|
|
189
|
+
|
|
190
|
+
Yields:
|
|
191
|
+
dict: The issue type data.
|
|
192
|
+
"""
|
|
193
|
+
client = get_client(base_url, email, api_token)
|
|
194
|
+
yield from client.get_issue_types()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@dlt.resource(write_disposition="replace")
|
|
198
|
+
def statuses(
|
|
199
|
+
base_url: str = dlt.secrets.value,
|
|
200
|
+
email: str = dlt.secrets.value,
|
|
201
|
+
api_token: str = dlt.secrets.value,
|
|
202
|
+
) -> Iterable[TDataItem]:
|
|
203
|
+
"""
|
|
204
|
+
Fetches all statuses from Jira.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
base_url (str): Jira instance URL
|
|
208
|
+
email (str): User email for authentication
|
|
209
|
+
api_token (str): API token for authentication
|
|
210
|
+
|
|
211
|
+
Yields:
|
|
212
|
+
dict: The status data.
|
|
213
|
+
"""
|
|
214
|
+
client = get_client(base_url, email, api_token)
|
|
215
|
+
yield from client.get_statuses()
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
@dlt.resource(write_disposition="replace")
|
|
219
|
+
def priorities(
|
|
220
|
+
base_url: str = dlt.secrets.value,
|
|
221
|
+
email: str = dlt.secrets.value,
|
|
222
|
+
api_token: str = dlt.secrets.value,
|
|
223
|
+
) -> Iterable[TDataItem]:
|
|
224
|
+
"""
|
|
225
|
+
Fetches all priorities from Jira.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
base_url (str): Jira instance URL
|
|
229
|
+
email (str): User email for authentication
|
|
230
|
+
api_token (str): API token for authentication
|
|
231
|
+
|
|
232
|
+
Yields:
|
|
233
|
+
dict: The priority data.
|
|
234
|
+
"""
|
|
235
|
+
client = get_client(base_url, email, api_token)
|
|
236
|
+
yield from client.get_priorities()
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
@dlt.resource(write_disposition="replace")
|
|
240
|
+
def resolutions(
|
|
241
|
+
base_url: str = dlt.secrets.value,
|
|
242
|
+
email: str = dlt.secrets.value,
|
|
243
|
+
api_token: str = dlt.secrets.value,
|
|
244
|
+
) -> Iterable[TDataItem]:
|
|
245
|
+
"""
|
|
246
|
+
Fetches all resolutions from Jira.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
base_url (str): Jira instance URL
|
|
250
|
+
email (str): User email for authentication
|
|
251
|
+
api_token (str): API token for authentication
|
|
252
|
+
|
|
253
|
+
Yields:
|
|
254
|
+
dict: The resolution data.
|
|
255
|
+
"""
|
|
256
|
+
client = get_client(base_url, email, api_token)
|
|
257
|
+
yield from client.get_resolutions()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
@dlt.transformer(
|
|
261
|
+
data_from=projects,
|
|
262
|
+
write_disposition="replace",
|
|
263
|
+
)
|
|
264
|
+
@dlt.defer
|
|
265
|
+
def project_versions(
|
|
266
|
+
project: TDataItem,
|
|
267
|
+
base_url: str = dlt.secrets.value,
|
|
268
|
+
email: str = dlt.secrets.value,
|
|
269
|
+
api_token: str = dlt.secrets.value,
|
|
270
|
+
) -> Iterable[TDataItem]:
|
|
271
|
+
"""
|
|
272
|
+
Fetches versions for each project from Jira.
|
|
273
|
+
|
|
274
|
+
Args:
|
|
275
|
+
project (dict): The project data.
|
|
276
|
+
base_url (str): Jira instance URL
|
|
277
|
+
email (str): User email for authentication
|
|
278
|
+
api_token (str): API token for authentication
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
list[dict]: The version data for the given project.
|
|
282
|
+
"""
|
|
283
|
+
client = get_client(base_url, email, api_token)
|
|
284
|
+
project_key = project.get("key")
|
|
285
|
+
if not project_key:
|
|
286
|
+
return []
|
|
287
|
+
|
|
288
|
+
return list(client.get_project_versions(project_key))
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
@dlt.transformer(
|
|
292
|
+
data_from=projects,
|
|
293
|
+
write_disposition="replace",
|
|
294
|
+
)
|
|
295
|
+
@dlt.defer
|
|
296
|
+
def project_components(
|
|
297
|
+
project: TDataItem,
|
|
298
|
+
base_url: str = dlt.secrets.value,
|
|
299
|
+
email: str = dlt.secrets.value,
|
|
300
|
+
api_token: str = dlt.secrets.value,
|
|
301
|
+
) -> Iterable[TDataItem]:
|
|
302
|
+
"""
|
|
303
|
+
Fetches components for each project from Jira.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
project (dict): The project data.
|
|
307
|
+
base_url (str): Jira instance URL
|
|
308
|
+
email (str): User email for authentication
|
|
309
|
+
api_token (str): API token for authentication
|
|
310
|
+
|
|
311
|
+
Returns:
|
|
312
|
+
list[dict]: The component data for the given project.
|
|
313
|
+
"""
|
|
314
|
+
client = get_client(base_url, email, api_token)
|
|
315
|
+
project_key = project.get("key")
|
|
316
|
+
if not project_key:
|
|
317
|
+
return []
|
|
318
|
+
|
|
319
|
+
return list(client.get_project_components(project_key))
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@dlt.resource(write_disposition="replace")
|
|
323
|
+
def events(
|
|
324
|
+
base_url: str = dlt.secrets.value,
|
|
325
|
+
email: str = dlt.secrets.value,
|
|
326
|
+
api_token: str = dlt.secrets.value,
|
|
327
|
+
) -> Iterable[TDataItem]:
|
|
328
|
+
"""
|
|
329
|
+
Fetches all event types from Jira (e.g., Issue Created, Issue Updated, etc.).
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
base_url (str): Jira instance URL
|
|
333
|
+
email (str): User email for authentication
|
|
334
|
+
api_token (str): API token for authentication
|
|
335
|
+
|
|
336
|
+
Yields:
|
|
337
|
+
dict: The event data.
|
|
338
|
+
"""
|
|
339
|
+
client = get_client(base_url, email, api_token)
|
|
340
|
+
yield from client.get_events()
|