Skip to content

Commit 80411d8

Browse files
committed
feat: release changes for version 0.4.4
1 parent b775d5f commit 80411d8

File tree

17 files changed

+461
-9
lines changed

17 files changed

+461
-9
lines changed

galaxy/core/galaxy.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import importlib
33
import json
44
import logging
5+
import time
56
import traceback
67
from collections import OrderedDict
78
from collections.abc import AsyncGenerator, Callable
@@ -146,7 +147,13 @@ async def run_integration_methods(
146147
async def _run_integration_method_and_push_to_magneto(method: Callable) -> None:
147148
logger.info("%-*s | Executing method", method_logger_width, method.__name__)
148149

150+
start_time = time.time()
149151
method_entities = [e async for e in _run_integration_method_to_entities(instance=instance, method=method)]
152+
end_time = time.time()
153+
logger.debug(
154+
"%-*s | Execution: %d ms", method_logger_width, method.__name__, int((end_time - start_time) * 1000)
155+
)
156+
150157
logger.info("%-*s | Entities found: %d", method_logger_width, method.__name__, len(method_entities))
151158
logger.debug("%-*s | Results: %r", method_logger_width, method.__name__, method_entities)
152159

galaxy/core/models.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from typing import Any, Literal
1+
from typing import Any, List, Literal
22

33
import yaml
4-
from pydantic import BaseModel, ConfigDict, Field
4+
from pydantic import BaseModel, ConfigDict, Field, RootModel
55

66
__all__ = ["Config", "ExecutionType", "RelyConfig", "IntegrationConfig", "SchedulerJobStates"]
77

@@ -51,3 +51,19 @@ def from_yaml(cls, file_path):
5151

5252
rely: RelyConfig
5353
integration: IntegrationConfig
54+
55+
56+
class FileCheck(BaseModel):
57+
path: str
58+
destination: str
59+
regex: str = Field(..., alias="regex")
60+
61+
62+
class FileCheckList(RootModel):
63+
root: List[FileCheck]
64+
65+
def __iter__(self):
66+
return iter(self.root)
67+
68+
def __getitem__(self, item):
69+
return self.root[item]

galaxy/core/utils.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
from aiohttp import ClientResponseError
1010
from fastapi import HTTPException, Request, Security
1111
from fastapi.security.api_key import APIKeyHeader
12+
from pydantic import ValidationError
1213

1314
from galaxy.core.magneto import Magneto
1415
from galaxy.core.mapper import Mapper
16+
from galaxy.core.models import Config, FileCheckList
1517

1618

1719
def from_env(variable, raise_exception=False) -> str | int:
@@ -94,3 +96,37 @@ async def get_api_key(api_key_header: str = Security(APIKeyHeader(name="authoriz
9496
return True
9597
else:
9698
raise HTTPException(status_code=403, detail="Could not validate credentials")
99+
100+
101+
def files_to_check(config: Config, logger: logging.Logger) -> list[dict]:
102+
files_to_check: list | str | FileCheckList = config.integration.properties.get("filesToCheck", "")
103+
104+
if isinstance(files_to_check, str):
105+
try:
106+
files_check_list = FileCheckList.model_validate_json(files_to_check)
107+
except ValidationError as e:
108+
files_from_str = [
109+
{"path": fc[0], "destination": fc[1], "regex": fc[2]}
110+
for fc in (file_check.split("::") for file_check in files_to_check.split("||"))
111+
if len(fc) == 3 and fc[0] and fc[1]
112+
]
113+
114+
try:
115+
files_check_list = FileCheckList.model_validate(files_from_str)
116+
except ValidationError:
117+
logger.error(f"Invalid file check config: {files_to_check}: {e}")
118+
return []
119+
120+
return list([fc.model_dump() for fc in files_check_list])
121+
122+
if isinstance(files_to_check, list):
123+
try:
124+
files_check_list = FileCheckList.model_validate(files_to_check)
125+
126+
return list([fc.model_dump() for fc in files_check_list])
127+
except ValidationError:
128+
logger.error(f"Invalid file check config: {files_to_check}")
129+
return []
130+
131+
logger.warning(f"Invalid file check config: {files_to_check}")
132+
return []

galaxy/integrations/github/.rely/automations.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,9 @@
251251
"closedAt": "{{ data.properties.closedAt if data.properties.closedAt else data.properties.mergedAt }}",
252252
"createdBy": "{{ data.relations.creator.value }}",
253253
"reviewers": "{{ data.relations.reviewers.value if data.relations.reviewers else [] }}",
254-
"firstReviewActivityAt": "{{ [data.properties.firstReviewAt, data.properties.closedAt] | select() | min | default(none) }}"
254+
"firstReviewActivityAt": "{{ [data.properties.firstReviewAt, data.properties.closedAt] | select() | min | default(none) }}",
255+
"additions": "{{ data.properties.additions if data.properties.additions else none }}",
256+
"deletions": " {{ data.properties.deletions if data.properties.deletions else none }}"
255257
}
256258
},
257259
"resourceType": "entity",

galaxy/integrations/github/.rely/blueprints.json

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,16 @@
222222
"format": "date-time",
223223
"title": "First Review At",
224224
"description": "The date of the first review activity on the pull request."
225+
},
226+
"additions": {
227+
"type": "integer",
228+
"title": "Number of Additions",
229+
"description": "The number of lines added in the pull request."
230+
},
231+
"deletions": {
232+
"type": "integer",
233+
"title": "Number of Deletions",
234+
"description": "The number of lines deleted in the pull request."
225235
}
226236
}
227237
},
@@ -730,5 +740,52 @@
730740
"array": false
731741
}
732742
}
743+
},
744+
{
745+
"id": "github.v1.repository_metrics",
746+
"title": "GitHub Repository Metrics",
747+
"description": "Blueprint defining GitHub Repository Metrics",
748+
"icon": "github",
749+
"schemaProperties": {
750+
"title": "Blueprint properties",
751+
"type": "object",
752+
"properties": {
753+
"metrics": {
754+
"type": "object",
755+
"title": "Metrics",
756+
"description": "Repository metrics data",
757+
"properties": {
758+
"dailyCommits": {
759+
"type": "array",
760+
"title": "Daily Commit Metrics",
761+
"description": "Daily commit counts for the repository",
762+
"items": {
763+
"type": "object",
764+
"properties": {
765+
"date": {
766+
"type": "string",
767+
"title": "Date",
768+
"description": "The date of the metric"
769+
},
770+
"count": {
771+
"type": "number",
772+
"title": "Count",
773+
"description": "Number of commits for this date"
774+
}
775+
}
776+
}
777+
}
778+
}
779+
}
780+
}
781+
},
782+
"relations": {
783+
"repository": {
784+
"value": "github.v1.repository",
785+
"title": "Repository",
786+
"description": "The project this metric belongs to",
787+
"array": false
788+
}
789+
}
733790
}
734791
]

galaxy/integrations/github/.rely/mappings.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ resources:
7878
| sort
7979
| first
8080
// null
81+
additions: .additions
82+
deletions: .deletions
8183
relations:
8284
repository:
8385
value: .context.repositoryId | tostring
@@ -221,3 +223,20 @@ resources:
221223
value: .context.environmentId | tostring
222224
triggeredBy:
223225
value: .creator.login
226+
227+
- kind: repository_metrics
228+
mappings:
229+
id: '.context.repositoryId | tostring + "-metrics"'
230+
title: '.context.repositoryName + " (Metrics)"'
231+
blueprintId: '"github.v1.repository_metrics"'
232+
properties:
233+
metrics:
234+
dailyCommits: |
235+
with_entries(select(.key == "commits"))
236+
| .commits
237+
| map(. + {date: .committedDate | fromdate | strftime("%Y-%m-%d")})
238+
| group_by(.date)
239+
| map({date: .[0].date, count: length})
240+
relations:
241+
repository:
242+
value: .context.repositoryId | tostring

galaxy/integrations/github/client.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import base64
22
from collections.abc import Iterable
33
from datetime import datetime, timedelta
4+
from functools import partial
45
from itertools import count
56
from types import TracebackType
67
from typing import Any
@@ -432,3 +433,42 @@ async def get_deployments(
432433
cursor = page_info["endCursor"]
433434

434435
return all_deployments
436+
437+
async def get_commits(
438+
self, organization: str, repo: str, branch: str, *, exclude_merge_commits: bool = True
439+
) -> list[dict[str, str | int]]:
440+
all_commits = []
441+
442+
query_builder = partial(
443+
build_graphql_query,
444+
query_type=QueryType.COMMITS,
445+
repo_id=self.build_repo_id(organization, repo),
446+
branch=branch,
447+
page_size=self.page_size,
448+
since=self.history_limit_timestamp,
449+
)
450+
451+
cursor = None
452+
while True:
453+
query = query_builder(after=cursor)
454+
response = await self._make_graphql_request(query)
455+
456+
try:
457+
data = response["data"]["repository"]["commits"]["history"] or {}
458+
commits = data["nodes"] or []
459+
all_commits.extend(
460+
[commit for commit in commits if not exclude_merge_commits or not self._is_merge_commit(commit)]
461+
)
462+
page_info = data["pageInfo"] or {}
463+
has_next_page = page_info.get("hasNextPage") or False
464+
cursor = page_info.get("endCursor")
465+
except (KeyError, TypeError):
466+
break
467+
468+
if not has_next_page:
469+
break
470+
471+
return all_commits
472+
473+
def _is_merge_commit(self, commit: dict) -> bool:
474+
return ((commit.get("parents") or {}).get("totalCount") or 0) > 1

galaxy/integrations/github/main.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,13 +84,15 @@ async def repository(self) -> None:
8484
)
8585

8686
for metadata in repositories_metadata:
87+
content = await self.client.get_repo(metadata["owner"]["login"], metadata["name"])
8788
self.repositories[metadata["id"]] = {
8889
"id": metadata["id"],
8990
"slug": metadata["name"],
9091
"owner": metadata["owner"]["login"],
9192
"link": metadata["html_url"],
9293
"metadata": metadata,
93-
"content": await self.client.get_repo(metadata["owner"]["login"], metadata["name"]),
94+
"default_branch": content.get("defaultBranchRef", {}).get("name"),
95+
"content": content,
9496
}
9597

9698
self.logger.info(f"Found {len(self.repositories)} repositories")
@@ -284,6 +286,23 @@ async def deployments(self) -> list[dict]:
284286

285287
return new_entities + deployments_mapped
286288

289+
@register(_methods, group=6)
290+
async def repository_metrics(self) -> list[dict]:
291+
all_metrics = []
292+
for repo in self.repositories.values():
293+
commits = await self.client.get_commits(repo["owner"], repo["slug"], branch=repo["default_branch"])
294+
repository_metrics = await self.mapper.process(
295+
"repository_metrics",
296+
[{"commits": commits}],
297+
context={"repositoryId": repo["id"], "repositoryName": repo["slug"]},
298+
)
299+
all_metrics.extend(repository_metrics)
300+
301+
self.logger.info(
302+
f"Calculated {len(all_metrics)} repository metrics from the last {self.client.days_of_history} days"
303+
)
304+
return all_metrics
305+
287306
async def register_inactive_users(self, inactive_usernames):
288307
if not inactive_usernames:
289308
return []

galaxy/integrations/github/queries.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from collections.abc import Iterable
2+
from datetime import datetime
23
from enum import Enum
34
from typing import Any, Optional
45

@@ -15,6 +16,7 @@ class QueryType(str, Enum):
1516
TEAMS = "teams"
1617
TEAM_MEMBERS = "team_members"
1718
TEAM_REPOS = "team_repos"
19+
COMMITS = "commits"
1820

1921

2022
def build_graphql_query(query_type: QueryType, **params: Any) -> dict:
@@ -27,6 +29,7 @@ def build_graphql_query(query_type: QueryType, **params: Any) -> dict:
2729
QueryType.TEAMS: _build_teams_query,
2830
QueryType.TEAM_MEMBERS: _build_team_members,
2931
QueryType.TEAM_REPOS: _build_team_repos,
32+
QueryType.COMMITS: _build_commits_query,
3033
}
3134

3235
query, variables = builders_by_query_type[query_type](**params)
@@ -176,6 +179,8 @@ def _build_pull_requests_query(
176179
login
177180
}
178181
}
182+
additions
183+
deletions
179184
}
180185
}
181186
pageInfo {
@@ -469,3 +474,53 @@ def _build_members_query(
469474

470475
variables = {"owner": owner, "after": after, "pageSize": page_size}
471476
return query, variables
477+
478+
479+
def _build_commits_query(
480+
repo_id: str, branch: str, *, after: str | None = None, since: datetime | None = None, page_size: int = 50
481+
) -> tuple[str, dict[str, str]]:
482+
query = """
483+
query GetCommits(
484+
$owner: String!
485+
$name: String!
486+
$branch: String!
487+
$since: GitTimestamp
488+
$after: String = null
489+
$pageSize: Int = 50
490+
) {
491+
repository(owner: $owner, name: $name, followRenames: true) {
492+
commits: object(expression: $branch) {
493+
... on Commit {
494+
history(first: $pageSize, after: $after, since: $since) {
495+
pageInfo {
496+
endCursor
497+
hasNextPage
498+
}
499+
nodes {
500+
oid
501+
committedDate
502+
author {
503+
user {
504+
login
505+
}
506+
}
507+
parents {
508+
totalCount
509+
}
510+
}
511+
}
512+
}
513+
}
514+
}
515+
}
516+
"""
517+
owner, name = repo_id.split("/", maxsplit=1)
518+
variables = {
519+
"owner": owner,
520+
"name": name,
521+
"branch": branch,
522+
"after": after,
523+
"pageSize": page_size,
524+
"since": since.isoformat() if since is not None else None,
525+
}
526+
return query, variables

0 commit comments

Comments
 (0)