diff --git a/erpnext_github_integration/api.py b/erpnext_github_integration/api.py
index ccebce7..07158c3 100644
--- a/erpnext_github_integration/api.py
+++ b/erpnext_github_integration/api.py
@@ -20,9 +20,10 @@ def validate_repository(doc, method):
def get_repository_dashboard_data(data):
"""Get dashboard data for Repository doctype"""
return {
- "heatmap": True,
- "heatmap_message": _("This is based on the commits in the repository"),
- "fieldname": "repository", # This is the default fieldname
+ "fieldname": "repository", # main link field for connections
+ "non_standard_fieldnames": {
+ "Task": "github_repo"
+ },
"transactions": [
{
"label": _("Issues & PRs"),
@@ -30,10 +31,7 @@ def get_repository_dashboard_data(data):
},
{
"label": _("Project Management"),
- "items": [
- {"item": "Project", "fieldname": "repository"}, # Project uses 'repository' field
- {"item": "Task", "fieldname": "github_repo"} # Task uses 'github_repo' field
- ]
+ "items": ["Project", "Task"]
}
]
}
@@ -310,9 +308,16 @@ def link_github_user_to_erp(github_username, erp_user):
user_doc = frappe.get_doc('User', erp_user)
user_doc.github_username = github_username
user_doc.save(ignore_permissions=True)
- return {'success': True, 'message': _('GitHub username linked successfully')}
- except Exception as e:
+ return {
+ 'success': True,
+ 'message': _('GitHub username {0} linked successfully to user {1}').format(
+ github_username, erp_user)
+ }
+ except frappe.ValidationError as e:
return {'success': False, 'error': str(e)}
+ except Exception as e:
+ frappe.log_error(f'Error linking GitHub user: {str(e)}')
+ return {'success': False, 'error': _('An error occurred while updating the user')}
@frappe.whitelist()
def get_repository_statistics(repo_full_name):
diff --git a/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.js b/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.js
index f16f3f4..058a240 100644
--- a/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.js
+++ b/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.js
@@ -3,6 +3,8 @@
frappe.ui.form.on("GitHub Settings", {
refresh(frm) {
+ // Update on form refresh
+ update_auth_fields_visibility(frm);
// Test Connection button
frm.add_custom_button(__('Test Connection'), function() {
if (!frm.doc.personal_access_token) {
@@ -22,6 +24,31 @@ frappe.ui.form.on("GitHub Settings", {
});
}, __('Actions'));
+ frm.add_custom_button(__('Bulk Update Github Username'), function() {
+ if (!frm.doc.personal_access_token) {
+ frappe.msgprint({
+ title: __('Personal Access Token Required'),
+ indicator: 'red',
+ message: __('Please set Personal Access Token first in the Personal Access Token field.')
+ });
+ return;
+ }
+
+ frappe.confirm(
+ __('This will update GitHub usernames for all users based on their email addresses. This may take several minutes. Continue?'),
+ function() {
+ // Proceed with bulk update
+ update_all_users_github_usernames();
+ },
+ function() {
+ frappe.show_alert({
+ message: __('Bulk update cancelled'),
+ indicator: 'blue'
+ });
+ }
+ );
+ }, __('Actions'));
+
// Fetch All Repositories button
frm.add_custom_button(__('Fetch All Repositories'), function() {
frappe.call({
@@ -225,6 +252,9 @@ frappe.ui.form.on("GitHub Settings", {
// Manage Repository Access button
frm.add_custom_button(__('Manage Repository Access'), function() {
let repos = [];
+ let users = [];
+
+ // Fetch repositories first
frappe.call({
method: 'frappe.client.get_list',
args: {
@@ -234,66 +264,88 @@ frappe.ui.form.on("GitHub Settings", {
callback: function(r) {
if (r.message && r.message.length) {
r.message.forEach(repo => {
- repos.push({label: repo.full_name, value: repo.full_name});
+ repos.push({ label: repo.full_name, value: repo.full_name });
});
-
- let d = new frappe.ui.Dialog({
- title: __('Manage Repository Access'),
- fields: [
- {
- fieldname: 'repository',
- fieldtype: 'Select',
- label: 'Repository',
- options: repos,
- reqd: 1
- },
- {
- fieldname: 'action',
- fieldtype: 'Select',
- label: 'Action',
- options: 'Add Collaborator\nRemove Collaborator\nAdd Team\nRemove Team',
- reqd: 1
- },
- {
- fieldname: 'identifier',
- fieldtype: 'Data',
- label: 'Username/Team Name',
- reqd: 1
- },
- {
- fieldname: 'permission',
- fieldtype: 'Select',
- label: 'Permission Level',
- options: 'pull\npush\nadmin\nmaintain\ntriage',
- default: 'push'
- }
- ],
- primary_action_label: __('Execute'),
- primary_action: function(values) {
- let action_map = {
- 'Add Collaborator': 'add_collaborator',
- 'Remove Collaborator': 'remove_collaborator',
- 'Add Team': 'add_team',
- 'Remove Team': 'remove_team'
- };
-
- frappe.call({
- method: 'erpnext_github_integration.github_api.manage_repo_access',
- args: {
- repo_full_name: values.repository,
- action: action_map[values.action],
- identifier: values.identifier,
- permission: values.permission
- },
- callback: function(r) {
- frappe.msgprint(__('Repository access updated successfully'));
- d.hide();
- }
+ }
+
+ // Fetch users after repos
+ frappe.call({
+ method: 'frappe.client.get_list',
+ args: {
+ doctype: 'User',
+ filters: { enabled: 1 },
+ fields: ['name', 'full_name']
+ },
+ callback: function(r2) {
+ if (r2.message && r2.message.length) {
+ r2.message.forEach(user => {
+ users.push({
+ label: user.full_name || user.name,
+ value: user.name
+ });
});
}
- });
- d.show();
- }
+
+ // Now show the dialog
+ let d = new frappe.ui.Dialog({
+ title: __('Manage Repository Access'),
+ fields: [
+ {
+ fieldname: 'repository',
+ fieldtype: 'Select',
+ label: 'Repository',
+ options: repos,
+ reqd: 1
+ },
+ {
+ fieldname: 'action',
+ fieldtype: 'Select',
+ label: 'Action',
+ options: 'Add Collaborator\nRemove Collaborator\nAdd Team\nRemove Team',
+ reqd: 1
+ },
+ {
+ fieldname: 'identifier',
+ fieldtype: 'Select',
+ label: 'Username',
+ options: users,
+ reqd: 1
+ },
+ {
+ fieldname: 'permission',
+ fieldtype: 'Select',
+ label: 'Permission Level',
+ options: 'pull\npush\nadmin\nmaintain\ntriage',
+ default: 'push'
+ }
+ ],
+ primary_action_label: __('Execute'),
+ primary_action: function(values) {
+ let action_map = {
+ 'Add Collaborator': 'add_collaborator',
+ 'Remove Collaborator': 'remove_collaborator',
+ 'Add Team': 'add_team',
+ 'Remove Team': 'remove_team'
+ };
+
+ frappe.call({
+ method: 'erpnext_github_integration.github_api.manage_repo_access',
+ args: {
+ repo_full_name: values.repository,
+ action: action_map[values.action],
+ identifier: values.identifier,
+ permission: values.permission
+ },
+ callback: function(r) {
+ frappe.msgprint(__('Repository access updated successfully'));
+ d.hide();
+ }
+ });
+ }
+ });
+ d.show();
+ }
+ });
}
});
}, __('Actions'));
@@ -359,17 +411,146 @@ frappe.ui.form.on("GitHub Settings", {
}, __('Statistics'));
}
},
+ onload: function(frm) {
+ // Set initial state
+ update_auth_fields_visibility(frm);
+ },
- auth_type(frm) {
- // Toggle field visibility based on auth type
- if (frm.doc.auth_type === 'Personal Access Token') {
- frm.toggle_display('personal_access_token', true);
- frm.toggle_display('oauth_client_id', false);
- frm.toggle_display('oauth_client_secret', false);
- } else if (frm.doc.auth_type === 'OAuth App') {
- frm.toggle_display('personal_access_token', false);
- frm.toggle_display('oauth_client_id', true);
- frm.toggle_display('oauth_client_secret', true);
- }
+ auth_type: function(frm) {
+ // Update when auth_type changes
+ update_auth_fields_visibility(frm);
}
-});
\ No newline at end of file
+});
+
+function update_auth_fields_visibility(frm) {
+ // Default to PAT if not set
+ const auth_type = frm.doc.auth_type || 'Personal Access Token';
+
+ if (auth_type === 'Personal Access Token') {
+ frm.set_df_property('personal_access_token', 'hidden', false);
+ frm.set_df_property('oauth_client_id', 'hidden', true);
+ frm.set_df_property('oauth_client_secret', 'hidden', true);
+ } else if (auth_type === 'OAuth App') {
+ frm.set_df_property('personal_access_token', 'hidden', true);
+ frm.set_df_property('oauth_client_id', 'hidden', false);
+ frm.set_df_property('oauth_client_secret', 'hidden', false);
+ }
+
+ // Refresh the form to apply changes
+ frm.refresh_fields();
+}
+
+// Script to bulk update GitHub usernames for all users
+function update_all_users_github_usernames() {
+ frappe.call({
+ method: 'frappe.client.get_list',
+ args: {
+ doctype: 'User',
+ fields: ['name', 'email', 'github_username', 'full_name'],
+ filters: [
+ ['email', '!=', ''],
+ ['github_username', '=', ''],
+ ['enabled', '=', 1]
+ ],
+ limit_page_length: 0
+ },
+ callback: function(r) {
+ if (r.message && r.message.length > 0) {
+ let users = r.message;
+ let processed = 0;
+ let successCount = 0;
+ let errorCount = 0;
+
+ // Show progress dialog
+ let progress_dialog = new frappe.ui.Dialog({
+ title: __('Updating GitHub Usernames'),
+ fields: [
+ {
+ fieldname: 'progress',
+ fieldtype: 'HTML',
+ options: `
+
${__('Processing 0 of ' + users.length + ' users...')}
+
+
`
+ }
+ ]
+ });
+
+ progress_dialog.show();
+
+ // Process users with delay to avoid rate limiting
+ users.forEach((user, index) => {
+ setTimeout(() => {
+ frappe.call({
+ method: 'erpnext_github_integration.github_api.get_github_username_by_email',
+ args: {
+ email: user.email
+ },
+ callback: function(response) {
+ processed++;
+
+ // Update progress
+ let progressPercent = (processed / users.length) * 100;
+ progress_dialog.fields_dict.progress.$wrapper.html(`
+
+
${__('Processing ' + processed + ' of ' + users.length + ' users...')}
+
+
+ ${__('Success:')} ${successCount} | ${__('Errors:')} ${errorCount}
+
+
+ `);
+
+ if (response.message && response.message.success) {
+ frappe.call({
+ method: 'erpnext_github_integration.api.link_github_user_to_erp',
+ args: {
+ erp_user: user.name,
+ github_username: response.message.github_username
+ },
+ callback: function(linkResponse) {
+ if (linkResponse.message && linkResponse.message.success) {
+ console.log(`Updated ${user.name} with GitHub username: ${response.message.github_username}`);
+ successCount++;
+ } else {
+ console.error(__('Failed to update {0}: {1}', [user.name, linkResponse.message?.error || 'Unknown error']));
+ errorCount++;
+ }
+
+ checkCompletion();
+ }
+ });
+ } else {
+ console.error(__('Failed to fetch GitHub username for {0}: {1}', [user.email, response.message?.error || 'Unknown error']));
+ errorCount++;
+ checkCompletion();
+ }
+
+ function checkCompletion() {
+ if (processed === users.length) {
+ progress_dialog.hide();
+ frappe.msgprint({
+ title: __('Update Complete'),
+ indicator: 'green',
+ message: __(`
+ Processed: ${processed} users
+ Success: ${successCount}
+ Errors: ${errorCount}
+ `)
+ });
+ }
+ }
+ }
+ });
+ }, index * 1500); // 1.5 second delay between requests to avoid GitHub rate limiting
+ });
+ } else {
+ frappe.msgprint(__('No users found without GitHub usernames'));
+ }
+ }
+ });
+}
\ No newline at end of file
diff --git a/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.json b/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.json
index a42a5c8..38e7f3b 100644
--- a/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.json
+++ b/erpnext_github_integration/erpnext_github_integration/doctype/github_settings/github_settings.json
@@ -12,8 +12,6 @@
"column_break_nwhs",
"default_organization",
"default_visibility",
- "sync_interval_minutes",
- "rate_limit_threshold",
"last_sync",
"enabled"
],
@@ -59,16 +57,6 @@
"label": "Default Visibility",
"options": "Public\nPrivate"
},
- {
- "fieldname": "sync_interval_minutes",
- "fieldtype": "Int",
- "label": "Sync Interval Minutes"
- },
- {
- "fieldname": "rate_limit_threshold",
- "fieldtype": "Int",
- "label": "Rate Limit Threshold"
- },
{
"fieldname": "last_sync",
"fieldtype": "Datetime",
@@ -83,7 +71,7 @@
],
"issingle": 1,
"links": [],
- "modified": "2025-08-20 11:42:40.710361",
+ "modified": "2025-08-21 14:54:53.501895",
"modified_by": "Administrator",
"module": "Erpnext Github Integration",
"name": "GitHub Settings",
diff --git a/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.js b/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.js
index 63a9436..2e2a229 100644
--- a/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.js
+++ b/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.js
@@ -149,26 +149,52 @@ frappe.ui.form.on("Repository", {
frm.add_custom_button(__('Show Activity'), function() {
frappe.call({
method: 'erpnext_github_integration.github_api.get_repository_activity',
- args: {repo_full_name: frm.doc.full_name, days: 30},
+ args: {repository: frm.doc.full_name, days: 30},
callback: function(r) {
- if (r.message) {
+ if (r.message && !r.message.error) {
let activity = r.message;
let html = '';
- if (activity.commits && activity.commits.length) {
+ // Summary statistics
+ html += `
+ Activity Summary (Last ${activity.period_days} days):
+ Commits: ${activity.commits} | Issues: ${activity.issues} | Pull Requests: ${activity.pulls}
+
`;
+
+ // Recent commits
+ if (activity.details && activity.details.commits && activity.details.commits.length) {
html += '
Recent Commits
';
- activity.commits.slice(0, 10).forEach(commit => {
- html += `- ${commit.commit.message.split('\n')[0]}
- by ${commit.commit.author.name} on ${frappe.datetime.str_to_user(commit.commit.author.date)} `;
+ activity.details.commits.forEach(commit => {
+ const message = commit.commit ? commit.commit.message : commit.message;
+ const author = commit.commit && commit.commit.author ? commit.commit.author.name :
+ commit.author ? commit.author.login : 'Unknown';
+ const date = commit.commit && commit.commit.author ? commit.commit.author.date :
+ commit.commit ? commit.commit.committer.date : '';
+
+ html += `- ${(message || '').split('\n')[0]}
+ by ${author} on ${date ? frappe.datetime.str_to_user(date) : 'unknown date'} `;
+ });
+ html += '
';
+ } else {
+ html += '
No recent commits
';
+ }
+
+ // Recent issues
+ if (activity.details && activity.details.issues && activity.details.issues.length) {
+ html += '
Recent Issues
';
+ activity.details.issues.forEach(issue => {
+ html += `- #${issue.number}: ${issue.title} - ${issue.state}
+ by ${issue.user ? issue.user.login : 'Unknown'} on ${frappe.datetime.str_to_user(issue.created_at)} `;
});
html += '
';
}
- if (activity.events && activity.events.length) {
- html += '
Recent Events
';
- activity.events.slice(0, 10).forEach(event => {
- html += `- ${event.type} by ${event.actor.login}
- ${frappe.datetime.str_to_user(event.created_at)} `;
+ // Recent pull requests
+ if (activity.details && activity.details.pulls && activity.details.pulls.length) {
+ html += 'Recent Pull Requests
';
+ activity.details.pulls.forEach(pr => {
+ html += `- #${pr.number}: ${pr.title} - ${pr.state}
+ by ${pr.user ? pr.user.login : 'Unknown'} on ${frappe.datetime.str_to_user(pr.created_at)} `;
});
html += '
';
}
@@ -176,7 +202,7 @@ frappe.ui.form.on("Repository", {
html += '
';
let d = new frappe.ui.Dialog({
- title: __('Repository Activity (Last 30 days)'),
+ title: __('Repository Activity (Last {0} days)', [activity.period_days]),
fields: [{
fieldname: 'activity',
fieldtype: 'HTML',
@@ -185,6 +211,8 @@ frappe.ui.form.on("Repository", {
size: 'large'
});
d.show();
+ } else {
+ frappe.msgprint(__('Failed to load activity: {0}', [r.message ? r.message.error : 'Unknown error']));
}
}
});
diff --git a/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.json b/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.json
index 98abc4b..90c49e0 100644
--- a/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.json
+++ b/erpnext_github_integration/erpnext_github_integration/doctype/repository/repository.json
@@ -10,6 +10,7 @@
"project",
"is_synced",
"last_synced",
+ "enabled",
"column_break_dsmh",
"repo_name",
"repo_owner",
@@ -103,10 +104,16 @@
"fieldname": "member_section",
"fieldtype": "Section Break",
"label": "Member"
+ },
+ {
+ "default": "1",
+ "fieldname": "enabled",
+ "fieldtype": "Check",
+ "label": "Enabled"
}
],
"links": [],
- "modified": "2025-08-20 15:52:23.049815",
+ "modified": "2025-08-21 11:56:17.540867",
"modified_by": "Administrator",
"module": "Erpnext Github Integration",
"name": "Repository",
diff --git a/erpnext_github_integration/erpnext_github_integration/doctype/repository_issue/repository_issue.json b/erpnext_github_integration/erpnext_github_integration/doctype/repository_issue/repository_issue.json
index 656a244..c7686eb 100644
--- a/erpnext_github_integration/erpnext_github_integration/doctype/repository_issue/repository_issue.json
+++ b/erpnext_github_integration/erpnext_github_integration/doctype/repository_issue/repository_issue.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "field:title",
+ "autoname": "format:{repository}-#{issue_number}",
"creation": "2025-08-12 18:03:45.384921",
"doctype": "DocType",
"engine": "InnoDB",
@@ -31,15 +31,16 @@
{
"fieldname": "issue_number",
"fieldtype": "Int",
+ "in_filter": 1,
"in_list_view": 1,
+ "in_standard_filter": 1,
"label": "Issue Number",
"reqd": 1
},
{
"fieldname": "title",
"fieldtype": "Data",
- "label": "Title",
- "unique": 1
+ "label": "Title"
},
{
"fieldname": "body",
@@ -94,11 +95,11 @@
}
],
"links": [],
- "modified": "2025-08-20 15:55:01.486955",
+ "modified": "2025-08-21 14:06:23.824247",
"modified_by": "Administrator",
"module": "Erpnext Github Integration",
"name": "Repository Issue",
- "naming_rule": "By fieldname",
+ "naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext_github_integration/erpnext_github_integration/doctype/repository_pull_request/repository_pull_request.json b/erpnext_github_integration/erpnext_github_integration/doctype/repository_pull_request/repository_pull_request.json
index 284d158..53bdd42 100644
--- a/erpnext_github_integration/erpnext_github_integration/doctype/repository_pull_request/repository_pull_request.json
+++ b/erpnext_github_integration/erpnext_github_integration/doctype/repository_pull_request/repository_pull_request.json
@@ -1,6 +1,6 @@
{
"actions": [],
- "autoname": "field:title",
+ "autoname": "format:{repository}-#{pr_number}",
"creation": "2025-08-12 18:03:44.866127",
"doctype": "DocType",
"engine": "InnoDB",
@@ -26,8 +26,8 @@
{
"fieldname": "title",
"fieldtype": "Data",
- "label": "Title",
- "unique": 1
+ "in_list_view": 1,
+ "label": "Title"
},
{
"fieldname": "repository",
@@ -40,7 +40,9 @@
{
"fieldname": "pr_number",
"fieldtype": "Int",
+ "in_filter": 1,
"in_list_view": 1,
+ "in_standard_filter": 1,
"label": "PR Number",
"reqd": 1
},
@@ -112,11 +114,11 @@
}
],
"links": [],
- "modified": "2025-08-20 15:54:43.666661",
+ "modified": "2025-08-21 14:46:22.038600",
"modified_by": "Administrator",
"module": "Erpnext Github Integration",
"name": "Repository Pull Request",
- "naming_rule": "By fieldname",
+ "naming_rule": "Expression",
"owner": "Administrator",
"permissions": [
{
diff --git a/erpnext_github_integration/github_api.py b/erpnext_github_integration/github_api.py
index 60ea711..deb5cdd 100644
--- a/erpnext_github_integration/github_api.py
+++ b/erpnext_github_integration/github_api.py
@@ -1,6 +1,7 @@
import frappe, json
from frappe import _
import datetime
+from datetime import datetime, timedelta
from dateutil import parser
from .github_client import github_request
@@ -63,6 +64,35 @@ def test_connection():
except Exception as e:
return {'success': False, 'error': str(e)}
+@frappe.whitelist()
+def get_github_username_by_email(email):
+ """Fetch GitHub username from GitHub API using email"""
+ settings = frappe.get_single('GitHub Settings')
+ token = settings.get_password('personal_access_token')
+
+ if not token:
+ return {'success': False, 'error': 'GitHub Personal Access Token not configured'}
+
+ try:
+ # Use github_request to search for users by email
+ search_results = github_request('GET', f'/search/users?q={email}+in:email', token)
+
+ if search_results and search_results.get('total_count', 0) > 0 and search_results.get('items'):
+ # Return the first matching username
+ return {
+ 'success': True,
+ 'github_username': search_results['items'][0]['login'],
+ 'total_results': search_results['total_count']
+ }
+ else:
+ return {
+ 'success': False,
+ 'error': f'No GitHub user found with email: {email}'
+ }
+
+ except Exception as e:
+ return {'success': False, 'error': str(e)}
+
@frappe.whitelist()
def fetch_all_repositories(organization=None):
"""Fetch all repositories from GitHub and create/update them in ERPNext"""
@@ -302,12 +332,14 @@ def sync_repo(repository):
# Clear and update members
repo_doc.set('members_table', [])
for m in members:
+ user_info = github_request('GET', f"/users/{m.get('login')}", token)
+ m_email = user_info.get("email") or ""
repo_doc.append('members_table', {
'repo_full_name': repo_full,
'github_username': m.get('login'),
'github_id': str(m.get('id', '')),
'role': 'maintainer' if m.get('permissions', {}).get('admin') else 'member',
- 'email': m.get('email') or ''
+ 'email': m_email or ''
})
repo_doc.save(ignore_permissions=True)
@@ -572,14 +604,15 @@ def sync_repo_members(repo_full_name):
try:
repo_doc = frappe.get_doc('Repository', {'full_name': repo_full_name})
repo_doc.set('members_table', [])
-
for m in members or []:
+ user_info = github_request('GET', f"/users/{m.get('login')}", token)
+ m_email = user_info.get("email") or ""
repo_doc.append('members_table', {
'repo_full_name': repo_full_name,
'github_username': m.get('login'),
'github_id': str(m.get('id', '')),
'role': 'maintainer' if m.get('permissions', {}).get('admin') else 'member',
- 'email': m.get('email') or ''
+ 'email': m_email or ''
})
repo_doc.save(ignore_permissions=True)
@@ -594,19 +627,24 @@ def sync_repo_members(repo_full_name):
proj.set('project_users', [])
for m in members or []:
+ user_info = github_request('GET', f"/users/{m.get('login')}", token)
+ m_email = user_info.get("email") or ""
username = m.get('login')
erp_user = None
# Try to find matching ERP user
try:
- user_doc = frappe.get_doc('User', {'github_username': username})
- erp_user = user_doc.name
+ user_name = frappe.db.get_value("User", {"github_username": username}, "name")
+ erp_user = user_name
except Exception:
# Try to find by email if available
- if m.get('email'):
+ if m_email:
try:
- user_doc = frappe.get_doc('User', m.get('email'))
- erp_user = user_doc.name
+ user_name = frappe.db.get_value("User", {"email": m_email}, "name")
+ if user_name:
+ user_doc = frappe.get_doc("User", user_name)
+ user_doc.github_username = username
+ user_doc.save(ignore_permissions=True)
except Exception:
pass
@@ -667,33 +705,72 @@ def sync_all_repositories():
except Exception as e:
results['failed'] += 1
frappe.log_error(message=str(e), title=f'GitHub Sync Error - {r.get("full_name")}')
-
+
+ settings = frappe.get_single("GitHub Settings")
+ settings.last_sync = frappe.utils.now()
+ settings.save(ignore_permissions=True)
+
return results
@frappe.whitelist()
-def get_repository_activity(repo_full_name, days=30):
+def get_repository_activity(repository, days=30):
"""Get recent activity for a repository"""
- settings = frappe.get_single('GitHub Settings')
- token = settings.get_password('personal_access_token')
- if not token:
- frappe.throw(_('GitHub Personal Access Token not configured in GitHub Settings'))
-
- from datetime import datetime, timedelta
- since = (datetime.now() - timedelta(days=days)).isoformat()
-
try:
- commits = github_request('GET', f"/repos/{repo_full_name}/commits", token,
- params={'since': since, 'per_page': 50})
- events = github_request('GET', f"/repos/{repo_full_name}/events", token,
- params={'per_page': 50})
+ settings = frappe.get_single('GitHub Settings')
+ token = settings.get_password('personal_access_token')
+
+ # Validate and convert days
+ try:
+ days_int = int(days)
+ except (ValueError, TypeError):
+ days_int = 30 # Default fallback
+
+ # Ensure days is within reasonable bounds
+ days_int = max(1, min(days_int, 365)) # Between 1 and 365 days
+
+ # Calculate since date
+ since = (datetime.now() - timedelta(days=days_int)).isoformat()
+
+ # Get activity data
+ commits = github_request(
+ 'GET',
+ f'/repos/{repository}/commits',
+ token,
+ params={'since': since, 'per_page': 50}
+ ) or []
+
+ issues = github_request(
+ 'GET',
+ f'/repos/{repository}/issues',
+ token,
+ params={'since': since, 'state': 'all', 'per_page': 20}
+ ) or []
+
+ pulls = github_request(
+ 'GET',
+ f'/repos/{repository}/pulls',
+ token,
+ params={'since': since, 'state': 'all', 'per_page': 20}
+ ) or []
+
+ # Filter out pull requests from issues (GitHub API returns PRs in issues)
+ actual_issues = [issue for issue in issues if 'pull_request' not in issue]
return {
- 'commits': commits or [],
- 'events': events or [],
- 'period_days': days
+ 'commits': len(commits),
+ 'issues': len(actual_issues),
+ 'pulls': len(pulls),
+ 'period_days': days_int,
+ 'details': {
+ 'commits': commits[:10], # Return first 10 for preview
+ 'issues': actual_issues[:10],
+ 'pulls': pulls[:10]
+ }
}
+
except Exception as e:
- frappe.throw(_('Failed to get repository activity: {0}').format(str(e)))
+ frappe.logger().error(f"Error getting repository activity: {str(e)}")
+ return {'error': str(e)}
@frappe.whitelist()
def create_repository_webhook(repo_full_name, webhook_url=None, events=None):