diff --git a/assistant/package.json b/assistant/package.json index 39d88ded..352643b9 100644 --- a/assistant/package.json +++ b/assistant/package.json @@ -91,6 +91,7 @@ "lint-staged": "^13.0.3", "postcss": "^8.4.49", "postcss-cli": "^11.0.0", + "postcss-nested": "^7.0.2", "postcss-prefix-selector": "^2.1.0", "prettier": "^2.7.1", "prettier-plugin-organize-imports": "^3.0.0", diff --git a/assistant/postcss.config.js b/assistant/postcss.config.js index 44eb5f6e..b8a28cf8 100644 --- a/assistant/postcss.config.js +++ b/assistant/postcss.config.js @@ -1,6 +1,7 @@ module.exports = { plugins: [ require('tailwindcss'), - require('autoprefixer') + require('autoprefixer'), + require("postcss-nested"), ] }; diff --git a/assistant/src/Assistant/index.tsx b/assistant/src/Assistant/index.tsx index f6097b0f..cb86e702 100644 --- a/assistant/src/Assistant/index.tsx +++ b/assistant/src/Assistant/index.tsx @@ -67,7 +67,7 @@ const Assistant = (props: AssistantProps) => { ); return ( -
+
= memo( // ============================ Render ============================ return (
{ }, []); return ( -
+
diff --git a/assistant/src/StarterList/index.tsx b/assistant/src/StarterList/index.tsx index 50a297fd..21b99200 100644 --- a/assistant/src/StarterList/index.tsx +++ b/assistant/src/StarterList/index.tsx @@ -10,7 +10,7 @@ export interface IProps { const StarterList: FC = ({ starters, onClick, style, className }) => { return ( -
+
= (params) => { }, []); return ( -
+
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"]), + }