+
List[Dict[str, int]]:
+ url = f"https://oss.open-digger.cn/github/{repo_name}/activity_details.json"
+
+ try:
+ response = requests.get(url)
+ data = response.json()
+ if not data:
+ return []
+
+ # Filter out only the monthly data (excluding quarters)
+ monthly_data = {k: v for k, v in data.items() if "-" in k}
+
+ # Get the most recent month
+ most_recent_month_key = max(monthly_data.keys())
+
+ # Return the data for the most recent month
+ return [
+ {"user": user, "value": value}
+ for user, value in monthly_data[most_recent_month_key]
+ ]
+ except Exception as e:
+ print(e)
+ return []
diff --git a/server/insight/service/issue.py b/server/insight/service/issue.py
new file mode 100644
index 00000000..2ebf2aba
--- /dev/null
+++ b/server/insight/service/issue.py
@@ -0,0 +1,11 @@
+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
diff --git a/server/insight/service/pr.py b/server/insight/service/pr.py
new file mode 100644
index 00000000..7044965a
--- /dev/null
+++ b/server/insight/service/pr.py
@@ -0,0 +1,18 @@
+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)
diff --git a/server/main.py b/server/main.py
index 42afff0e..d7d8a35b 100644
--- a/server/main.py
+++ b/server/main.py
@@ -20,6 +20,7 @@
from rag import router as rag_router
from task import router as task_router
from user import router as user_router
+from insight import router as insight_router
AUTH0_DOMAIN = get_env_variable("AUTH0_DOMAIN")
API_AUDIENCE = get_env_variable("API_IDENTIFIER")
@@ -67,6 +68,7 @@
app.include_router(github_app_router.router)
app.include_router(aws_router.router)
app.include_router(user_router.router)
+app.include_router(insight_router.router)
@app.get("/")
diff --git a/server/tests/insight/test_activity.py b/server/tests/insight/test_activity.py
new file mode 100644
index 00000000..cf0cb020
--- /dev/null
+++ b/server/tests/insight/test_activity.py
@@ -0,0 +1,45 @@
+import unittest
+from unittest.mock import patch, MagicMock
+from insight.service.activity import get_activity_data
+
+
+class TestGetActivityData(unittest.TestCase):
+
+ @patch("insight.service.activity.requests.get")
+ def test_get_activity_data(self, mock_get):
+ mock_response = MagicMock()
+ mock_response.json.return_value = {
+ "2023-12": [("user1", 10), ("user2", 5)],
+ "2024-01": [("user3", 20)],
+ "2024-02": [("user4", 25)],
+ "2024-03": [("user5", 30)],
+ }
+ mock_get.return_value = mock_response
+ repo_name = "petercat-ai/petercat"
+ expected_result = [{"user": "user5", "value": 30}]
+
+ result = get_activity_data(repo_name)
+
+ self.assertIsInstance(result, list)
+ self.assertEqual(result, expected_result)
+
+ @patch("insight.service.activity.requests.get")
+ def test_get_activity_data_empty(self, mock_get):
+ mock_response = MagicMock()
+ mock_response.json.return_value = {}
+ mock_get.return_value = mock_response
+ repo_name = "petercat-ai/petercat"
+ result = get_activity_data(repo_name)
+
+ self.assertEqual(result, [])
+
+ @patch("insight.service.activity.requests.get")
+ def test_get_activity_data_invalid_json(self, mock_get):
+
+ mock_response = MagicMock()
+ mock_response.json.side_effect = ValueError("Invalid JSON")
+ mock_get.return_value = mock_response
+
+ repo_name = "petercat-ai/petercat"
+ with self.assertRaises(ValueError):
+ get_activity_data(repo_name)
diff --git a/server/tests/utils/test_insight.py b/server/tests/utils/test_insight.py
new file mode 100644
index 00000000..acbef380
--- /dev/null
+++ b/server/tests/utils/test_insight.py
@@ -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)
diff --git a/server/utils/insight.py b/server/utils/insight.py
new file mode 100644
index 00000000..544098c9
--- /dev/null
+++ b/server/utils/insight.py
@@ -0,0 +1,61 @@
+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})"
+ )
+ return {
+ "year": [],
+ "quarter": [],
+ "month": [],
+ }
+
+ 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"]),
+ }