-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add API for data insights on PRs, issues, and code changes. (#700)
* feat: init the insight about issue * feat: add the api for pr&issue&code insight * chore: add tests for the insight utils
- Loading branch information
1 parent
80e5c80
commit 205e146
Showing
6 changed files
with
222 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import json | ||
from typing import Optional | ||
from fastapi import APIRouter, Depends | ||
from insight.service.issue import get_issue_data | ||
from insight.service.pr import get_code_changes, get_pr_data | ||
|
||
|
||
router = APIRouter( | ||
prefix="/api/insight", | ||
tags=["insight"], | ||
responses={404: {"description": "Not found"}}, | ||
) | ||
|
||
|
||
@router.get("/issue") | ||
def get_issue_insight(repo_name: str): | ||
try: | ||
result = get_issue_data(repo_name) | ||
return { | ||
"success": True, | ||
"data": result, | ||
} | ||
|
||
except Exception as e: | ||
return json.dumps({"success": False, "message": str(e)}) | ||
|
||
|
||
@router.get("/pr") | ||
def get_pr_insight(repo_name: str): | ||
try: | ||
result = get_pr_data(repo_name) | ||
return { | ||
"success": True, | ||
"data": result, | ||
} | ||
|
||
except Exception as e: | ||
return json.dumps({"success": False, "message": str(e)}) | ||
|
||
|
||
@router.get("code_change") | ||
def get_code_change_insight(repo_name: str): | ||
try: | ||
result = get_code_changes(repo_name) | ||
return { | ||
"success": True, | ||
"data": result, | ||
} | ||
|
||
except Exception as e: | ||
return json.dumps({"success": False, "message": str(e)}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import requests | ||
from collections import defaultdict | ||
|
||
from utils.insight import get_data | ||
|
||
|
||
def get_issue_data(repo_name): | ||
metrics_mapping = { | ||
"issues_new": "open", | ||
"issues_closed": "close", | ||
"issue_comments": "comment", | ||
} | ||
issue_data = get_data(repo_name, metrics_mapping) | ||
return issue_data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import requests | ||
from collections import defaultdict | ||
|
||
from utils.insight import get_data | ||
|
||
|
||
def get_pr_data(repo_name): | ||
metrics_mapping = { | ||
"change_requests": "open", | ||
"change_requests_accepted": "merge", | ||
"change_requests_reviews": "reviews", | ||
} | ||
return get_data(repo_name, metrics_mapping) | ||
|
||
|
||
def get_code_changes(repo_name): | ||
metrics_mapping = { | ||
"code_change_lines_add": "add", | ||
"code_change_lines_remove": "remove", | ||
} | ||
return get_data(repo_name, metrics_mapping) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import unittest | ||
from unittest.mock import patch, Mock | ||
from collections import defaultdict | ||
|
||
from utils.insight import get_data | ||
|
||
|
||
class TestGetData(unittest.TestCase): | ||
|
||
@patch("requests.get") | ||
def test_get_data_success(self, mock_get): | ||
mock_response = Mock() | ||
mock_response.status_code = 200 | ||
mock_response.json.return_value = {"2023-01": 10, "2023-02": 20, "2023-03": 30} | ||
mock_get.return_value = mock_response | ||
|
||
repo_name = "test-repo" | ||
metrics_mapping = {"metric1": "sum", "metric2": "average"} | ||
|
||
expected_result = { | ||
"year": [ | ||
{"type": "sum", "date": "2023", "value": 60}, | ||
{"type": "average", "date": "2023", "value": 60}, | ||
], | ||
"quarter": [ | ||
{"type": "sum", "date": "2023Q1", "value": 60}, | ||
{"type": "average", "date": "2023Q1", "value": 60}, | ||
], | ||
"month": [ | ||
{"type": "sum", "date": "2023-01", "value": 10}, | ||
{"type": "average", "date": "2023-01", "value": 10}, | ||
{"type": "sum", "date": "2023-02", "value": 20}, | ||
{"type": "average", "date": "2023-02", "value": 20}, | ||
{"type": "sum", "date": "2023-03", "value": 30}, | ||
{"type": "average", "date": "2023-03", "value": 30}, | ||
], | ||
} | ||
|
||
result = get_data(repo_name, metrics_mapping) | ||
self.assertEqual(result, expected_result) | ||
|
||
@patch("requests.get") | ||
def test_get_data_failure(self, mock_get): | ||
mock_response = Mock() | ||
mock_response.status_code = 500 | ||
mock_get.return_value = mock_response | ||
|
||
repo_name = "test-repo" | ||
metrics_mapping = {"metric1": "sum"} | ||
|
||
expected_result = { | ||
"year": [], | ||
"quarter": [], | ||
"month": [], | ||
} | ||
|
||
result = get_data(repo_name, metrics_mapping) | ||
self.assertEqual(result, expected_result) | ||
|
||
@patch("requests.get") | ||
def test_get_data_empty_response(self, mock_get): | ||
# 模拟返回空数据 | ||
mock_response = Mock() | ||
mock_response.status_code = 200 | ||
mock_response.json.return_value = {} | ||
mock_get.return_value = mock_response | ||
|
||
repo_name = "test-repo" | ||
metrics_mapping = {"metric1": "sum"} | ||
|
||
expected_result = {"year": [], "quarter": [], "month": []} | ||
|
||
result = get_data(repo_name, metrics_mapping) | ||
self.assertEqual(result, expected_result) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import requests | ||
from collections import defaultdict | ||
|
||
|
||
def get_data(repo_name, metrics_mapping): | ||
""" | ||
:param repo_name: GitHub 仓库名 | ||
:param metrics_mapping: 指标名称与聚合类型的映射字典 | ||
:return: 按年、季度、月聚合的数据字典 | ||
""" | ||
base_url = f"https://oss.open-digger.cn/github/{repo_name}/" | ||
|
||
aggregated_data = { | ||
"year": defaultdict( | ||
lambda: {metric_type: 0 for metric_type in metrics_mapping.values()} | ||
), | ||
"quarter": defaultdict( | ||
lambda: {metric_type: 0 for metric_type in metrics_mapping.values()} | ||
), | ||
"month": defaultdict( | ||
lambda: {metric_type: 0 for metric_type in metrics_mapping.values()} | ||
), | ||
} | ||
|
||
for metric, metric_type in metrics_mapping.items(): | ||
url = f"{base_url}{metric}.json" | ||
response = requests.get(url) | ||
|
||
if response.status_code == 200: | ||
data = response.json() | ||
for date, value in data.items(): | ||
if "-" in date: | ||
year, month = date.split("-")[:2] | ||
quarter = f"{year}Q{(int(month) - 1) // 3 + 1}" | ||
|
||
# aggregate by year, quarter, and month | ||
aggregated_data["year"][year][metric_type] += value | ||
aggregated_data["quarter"][quarter][metric_type] += value | ||
aggregated_data["month"][date][metric_type] += value | ||
else: | ||
print( | ||
f"Error fetching data from {url} (status code: {response.status_code})" | ||
) | ||
|
||
def format_result(data): | ||
result = [] | ||
for date, counts in data.items(): | ||
for type_, value in counts.items(): | ||
result.append({"type": type_, "date": date, "value": value}) | ||
return result | ||
|
||
return { | ||
"year": format_result(aggregated_data["year"]), | ||
"quarter": format_result(aggregated_data["quarter"]), | ||
"month": format_result(aggregated_data["month"]), | ||
} |