Skip to content

Commit c8e0777

Browse files
Merge pull request #330 from mustafa3rsan/add-states-sourcetable-for-linear
Add states sourcetable for linear
2 parents 81de010 + 3946cf0 commit c8e0777

File tree

4 files changed

+64
-44
lines changed

4 files changed

+64
-44
lines changed

ingestr/src/linear/__init__.py

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dlt
44
import pendulum
55

6-
from .helpers import _normalize_issue, _normalize_team, _paginate
6+
from .helpers import _paginate, normalize_dictionaries
77

88

99
def _get_date_range(updated_at, start_date):
@@ -99,7 +99,25 @@ def _get_date_range(updated_at, start_date):
9999
}
100100
}
101101
"""
102-
102+
WORKFLOW_STATES_QUERY = """
103+
query WorkflowStates($cursor: String) {
104+
workflowStates(first: 50, after: $cursor) {
105+
nodes {
106+
archivedAt
107+
color
108+
createdAt
109+
id
110+
inheritedFrom { id }
111+
name
112+
position
113+
team { id }
114+
type
115+
updatedAt
116+
}
117+
pageInfo { hasNextPage endCursor }
118+
}
119+
}
120+
"""
103121

104122
@dlt.source(name="linear", max_table_nesting=0)
105123
def linear_source(
@@ -122,7 +140,7 @@ def issues(
122140
for item in _paginate(api_key, ISSUES_QUERY, "issues"):
123141
if pendulum.parse(item["updatedAt"]) >= current_start_date:
124142
if pendulum.parse(item["updatedAt"]) <= current_end_date:
125-
yield _normalize_issue(item)
143+
yield normalize_dictionaries(item)
126144

127145
@dlt.resource(name="projects", primary_key="id", write_disposition="merge")
128146
def projects(
@@ -139,7 +157,7 @@ def projects(
139157
for item in _paginate(api_key, PROJECTS_QUERY, "projects"):
140158
if pendulum.parse(item["updatedAt"]) >= current_start_date:
141159
if pendulum.parse(item["updatedAt"]) <= current_end_date:
142-
yield item
160+
yield normalize_dictionaries(item)
143161

144162
@dlt.resource(name="teams", primary_key="id", write_disposition="merge")
145163
def teams(
@@ -158,7 +176,7 @@ def teams(
158176
for item in _paginate(api_key, TEAMS_QUERY, "teams"):
159177
if pendulum.parse(item["updatedAt"]) >= current_start_date:
160178
if pendulum.parse(item["updatedAt"]) <= current_end_date:
161-
yield _normalize_team(item)
179+
yield normalize_dictionaries(item)
162180

163181
@dlt.resource(name="users", primary_key="id", write_disposition="merge")
164182
def users(
@@ -175,6 +193,23 @@ def users(
175193
for item in _paginate(api_key, USERS_QUERY, "users"):
176194
if pendulum.parse(item["updatedAt"]) >= current_start_date:
177195
if pendulum.parse(item["updatedAt"]) <= current_end_date:
178-
yield item
196+
yield normalize_dictionaries(item)
197+
198+
@dlt.resource(name="workflow_states", primary_key="id", write_disposition="merge")
199+
def workflow_states(
200+
updated_at: dlt.sources.incremental[str] = dlt.sources.incremental(
201+
"updatedAt",
202+
initial_value=start_date.isoformat(),
203+
end_value=end_date.isoformat() if end_date else None,
204+
range_start="closed",
205+
range_end="closed",
206+
),
207+
) -> Iterator[Dict[str, Any]]:
208+
current_start_date, current_end_date = _get_date_range(updated_at, start_date)
209+
210+
for item in _paginate(api_key, WORKFLOW_STATES_QUERY, "workflowStates"):
211+
if pendulum.parse(item["updatedAt"]) >= current_start_date:
212+
if pendulum.parse(item["updatedAt"]) <= current_end_date:
213+
yield normalize_dictionaries(item)
214+
return [issues, projects, teams, users, workflow_states]
179215

180-
return issues, projects, teams, users

ingestr/src/linear/helpers.py

Lines changed: 19 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -32,41 +32,24 @@ def _paginate(api_key: str, query: str, root: str) -> Iterator[Dict[str, Any]]:
3232
cursor = data["pageInfo"]["endCursor"]
3333

3434

35-
def _normalize_issue(item: Dict[str, Any]) -> Dict[str, Any]:
36-
field_mapping = {
37-
"assignee": "assignee_id",
38-
"creator": "creator_id",
39-
"state": "state_id",
40-
"cycle": "cycle_id",
41-
"project": "project_id",
42-
}
43-
for key, value in field_mapping.items():
44-
if item.get(key):
45-
item[value] = item[key]["id"]
46-
del item[key]
47-
else:
48-
item[value] = None
49-
del item[key]
50-
json_fields = [
51-
"comments",
52-
"subscribers",
53-
"attachments",
54-
"labels",
55-
"subtasks",
56-
"projects",
57-
"memberships",
58-
"members",
59-
]
60-
for field in json_fields:
61-
if item.get(field):
62-
item[f"{field}"] = item[field].get("nodes", [])
6335

64-
return item
6536

66-
67-
def _normalize_team(item: Dict[str, Any]) -> Dict[str, Any]:
68-
json_fields = ["memberships", "members", "projects"]
69-
for field in json_fields:
70-
if item.get(field):
71-
item[f"{field}"] = item[field].get("nodes", [])
72-
return item
37+
def normalize_dictionaries(item: Dict[str, Any]) -> Dict[str, Any]:
38+
"""
39+
Automatically normalize dictionary fields by detecting their structure:
40+
- Convert nested objects with 'id' field to {field_name}_id
41+
- Convert objects with 'nodes' field to arrays
42+
"""
43+
normalized_item = item.copy()
44+
45+
for key, value in list(normalized_item.items()):
46+
if isinstance(value, dict):
47+
# If the dict has an 'id' field, replace with {key}_id
48+
if 'id' in value:
49+
normalized_item[f"{key}_id"] = value['id']
50+
del normalized_item[key]
51+
# If the dict has 'nodes' field, extract the nodes array
52+
elif 'nodes' in value:
53+
normalized_item[key] = value['nodes']
54+
55+
return normalized_item

ingestr/src/sources.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3160,7 +3160,7 @@ def dlt_source(self, uri: str, table: str, **kwargs):
31603160
if api_key is None:
31613161
raise MissingValueError("api_key", "Linear")
31623162

3163-
if table not in ["issues", "projects", "teams", "users"]:
3163+
if table not in ["issues", "projects", "teams", "users", "workflow_states"]:
31643164
raise UnsupportedResourceError(table, "Linear")
31653165

31663166
start_date = kwargs.get("interval_start")

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@ googleapis-common-protos==1.69.0
214214
# google-api-core
215215
# grpc-google-iam-v1
216216
# grpcio-status
217+
greenlet==3.2.3
218+
# via sqlalchemy
217219
grpc-google-iam-v1==0.14.2
218220
# via google-cloud-spanner
219221
grpc-interceptor==0.15.4

0 commit comments

Comments
 (0)