Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 10 additions & 18 deletions erpnext_github_integration/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,27 +68,19 @@ def get_user_repositories():
except:
pass

# Get repositories where user is a member
# Get repositories where user is a member using SQL query
github_username = frappe.db.get_value('User', user, 'github_username')
if github_username:
member_repos = frappe.get_all('Repository Member',
filters={'github_username': github_username},
fields=['repo_full_name'])
# Use SQL to search for the github_username in the members JSON field
repos_with_member = frappe.db.sql("""
SELECT name, full_name, repo_name, url, visibility, last_synced
FROM `tabRepository`
WHERE members LIKE %s
""", f'%"github_username": "{github_username}"%', as_dict=True)

for member_repo in member_repos:
try:
repo = frappe.get_doc('Repository', {'full_name': member_repo.get('repo_full_name')})
if repo.name not in [r['name'] for r in user_repos]: # Avoid duplicates
user_repos.append({
'name': repo.name,
'full_name': repo.full_name,
'repo_name': repo.repo_name,
'url': repo.url,
'visibility': repo.visibility,
'last_synced': repo.last_synced
})
except:
pass
for repo in repos_with_member:
if repo.name not in [r['name'] for r in user_repos]:
user_repos.append(repo)

return user_repos

Expand Down
4 changes: 2 additions & 2 deletions erpnext_github_integration/github_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -797,8 +797,8 @@ def create_repository_webhook(repo_full_name, webhook_url=None, events=None):
}
}

if settings.webhook_secret:
payload['config']['secret'] = settings.webhook_secret
if settings.get_password('webhook_secret'):
payload['config']['secret'] = settings.get_password('webhook_secret')

try:
resp = github_request('POST', f"/repos/{repo_full_name}/hooks", token, data=payload)
Expand Down
162 changes: 112 additions & 50 deletions erpnext_github_integration/webhooks.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import frappe, hmac, hashlib, json
from frappe import _


@frappe.whitelist(allow_guest=True)
def github_webhook():
"""Handle GitHub webhook events"""
Expand Down Expand Up @@ -41,7 +42,7 @@ def github_webhook():

# Queue background job
frappe.enqueue(
"_process_github_webhook",
"erpnext_github_integration.webhooks._process_github_webhook",
queue='default',
event=event,
data=data,
Expand All @@ -54,10 +55,6 @@ def github_webhook():
except Exception as e:
frappe.log_error(f'Webhook processing error: {frappe.get_traceback()}', 'GitHub Webhook Error')
return {'error': str(e)}

except Exception as e:
frappe.log_error(f'Webhook processing error: {str(e)}', 'GitHub Webhook Error')
return {'error': str(e)}


def _process_github_webhook(event=None, data=None, repo_full_name=None):
Expand Down Expand Up @@ -91,10 +88,12 @@ def _handle_issues_event(data, repo_full_name):
issue = data.get('issue', {})

if not issue:
frappe.log_error('No issue data in webhook payload', 'GitHub Issues Webhook')
return

issue_number = issue.get('number')
if not issue_number:
frappe.log_error('No issue number in webhook payload', 'GitHub Issues Webhook')
return

try:
Expand All @@ -113,16 +112,19 @@ def _handle_issues_event(data, repo_full_name):
doc.state = issue.get('state', 'open')
doc.labels = ','.join([l.get('name', '') for l in issue.get('labels', [])])
doc.url = issue.get('html_url', '')
doc.updated_at = issue.get('updated_at')
doc.updated_at = frappe.utils.get_datetime(issue.get('updated_at'))

# Update assignees
# Clear and update assignees
doc.set('assignees_table', [])
for assignee in issue.get('assignees', []):
doc.append('assignees_table', {
'user': assignee.get('login', '')
})

doc.save(ignore_permissions=True)
doc.flags.ignore_permissions = True
doc.save()
frappe.db.commit()

else:
# Create new issue
doc = frappe.get_doc({
Expand All @@ -135,8 +137,8 @@ def _handle_issues_event(data, repo_full_name):
'labels': ','.join([l.get('name', '') for l in issue.get('labels', [])]),
'url': issue.get('html_url', ''),
'github_id': str(issue.get('id', '')),
'created_at': issue.get('created_at'),
'updated_at': issue.get('updated_at')
'created_at': frappe.utils.get_datetime(issue.get('created_at')),
'updated_at': frappe.utils.get_datetime(issue.get('updated_at'))
})

# Add assignees
Expand All @@ -145,25 +147,30 @@ def _handle_issues_event(data, repo_full_name):
'user': assignee.get('login', '')
})

doc.insert(ignore_permissions=True)
doc.flags.ignore_permissions = True
doc.insert()
frappe.db.commit()

elif action == 'deleted' and existing:
# Delete issue
frappe.delete_doc('Repository Issue', existing, ignore_permissions=True)
frappe.db.commit()

except Exception as e:
frappe.log_error(f'Error handling issue event: {str(e)}', 'GitHub Issues Webhook')
frappe.log_error(f'Error handling issue event: {frappe.get_traceback()}', 'GitHub Issues Webhook')

def _handle_pull_request_event(data, repo_full_name):
"""Handle GitHub pull request webhook events"""
action = data.get('action')
pr = data.get('pull_request', {})

if not pr:
frappe.log_error('No pull request data in webhook payload', 'GitHub PR Webhook')
return

pr_number = pr.get('number')
if not pr_number:
frappe.log_error('No PR number in webhook payload', 'GitHub PR Webhook')
return

try:
Expand All @@ -185,16 +192,19 @@ def _handle_pull_request_event(data, repo_full_name):
doc.author = pr.get('user', {}).get('login', '')
doc.mergeable_state = pr.get('mergeable_state', '')
doc.url = pr.get('html_url', '')
doc.updated_at = pr.get('updated_at')
doc.updated_at = frappe.utils.get_datetime(pr.get('updated_at'))

# Update reviewers
# Clear and update reviewers
doc.set('reviewers_table', [])
for reviewer in pr.get('requested_reviewers', []):
doc.append('reviewers_table', {
'user': reviewer.get('login', '')
})

doc.save(ignore_permissions=True)
doc.flags.ignore_permissions = True
doc.save()
frappe.db.commit()

else:
# Create new PR
doc = frappe.get_doc({
Expand All @@ -210,8 +220,8 @@ def _handle_pull_request_event(data, repo_full_name):
'mergeable_state': pr.get('mergeable_state', ''),
'github_id': str(pr.get('id', '')),
'url': pr.get('html_url', ''),
'created_at': pr.get('created_at'),
'updated_at': pr.get('updated_at')
'created_at': frappe.utils.get_datetime(pr.get('created_at')),
'updated_at': frappe.utils.get_datetime(pr.get('updated_at'))
})

# Add reviewers
Expand All @@ -220,10 +230,12 @@ def _handle_pull_request_event(data, repo_full_name):
'user': reviewer.get('login', '')
})

doc.insert(ignore_permissions=True)
doc.flags.ignore_permissions = True
doc.insert()
frappe.db.commit()

except Exception as e:
frappe.log_error(f'Error handling PR event: {str(e)}', 'GitHub PR Webhook')
frappe.log_error(f'Error handling PR event: {frappe.get_traceback()}', 'GitHub PR Webhook')

def _handle_push_event(data, repo_full_name):
"""Handle GitHub push webhook events"""
Expand All @@ -232,23 +244,44 @@ def _handle_push_event(data, repo_full_name):
branch_name = ref.replace('refs/heads/', '') if ref.startswith('refs/heads/') else None

if not branch_name:
frappe.log_error(f'Invalid ref format: {ref}', 'GitHub Push Webhook')
return

# Get repository document
repo_filters = {'full_name': repo_full_name}
if not frappe.db.exists('Repository', repo_filters):
frappe.log_error(f'Repository {repo_full_name} not found', 'GitHub Push Webhook')
return

repo_doc = frappe.get_doc('Repository', repo_filters)

# Update repository last sync time
repo_doc = frappe.get_doc('Repository', {'full_name': repo_full_name})
repo_doc.last_synced = frappe.utils.now()

# Update branch information if it exists in the branches table
for branch in repo_doc.branches_table:
if branch.branch_name == branch_name:
branch.commit_sha = data.get('after', '')
branch.last_updated = frappe.utils.now()
break
branch_updated = False
if hasattr(repo_doc, 'branches_table') and repo_doc.branches_table:
for branch in repo_doc.branches_table:
if branch.branch_name == branch_name:
branch.commit_sha = data.get('after', '')
branch.last_updated = frappe.utils.now()
branch_updated = True
break

# If branch doesn't exist in table, add it
if not branch_updated and hasattr(repo_doc, 'branches_table'):
repo_doc.append('branches_table', {
'branch_name': branch_name,
'commit_sha': data.get('after', ''),
'last_updated': frappe.utils.now()
})

repo_doc.flags.ignore_permissions = True
repo_doc.save()
frappe.db.commit()

repo_doc.save(ignore_permissions=True)

except Exception as e:
frappe.log_error(f'Error handling push event: {str(e)}', 'GitHub Push Webhook')
frappe.log_error(f'Error handling push event: {frappe.get_traceback()}', 'GitHub Push Webhook')

def _handle_member_event(data, repo_full_name):
"""Handle GitHub member webhook events"""
Expand All @@ -257,21 +290,30 @@ def _handle_member_event(data, repo_full_name):
member = data.get('member', {})

if not member:
frappe.log_error('No member data in webhook payload', 'GitHub Member Webhook')
return

username = member.get('login')
if not username:
frappe.log_error('No username in member data', 'GitHub Member Webhook')
return

repo_doc = frappe.get_doc('Repository', {'full_name': repo_full_name})
# Get repository document
repo_filters = {'full_name': repo_full_name}
if not frappe.db.exists('Repository', repo_filters):
frappe.log_error(f'Repository {repo_full_name} not found', 'GitHub Member Webhook')
return

repo_doc = frappe.get_doc('Repository', repo_filters)

if action == 'added':
# Add member to repository
# Check if member already exists
existing_member = None
for mem in repo_doc.members_table:
if mem.github_username == username:
existing_member = mem
break
if hasattr(repo_doc, 'members_table') and repo_doc.members_table:
for mem in repo_doc.members_table:
if mem.github_username == username:
existing_member = mem
break

if not existing_member:
repo_doc.append('members_table', {
Expand All @@ -280,38 +322,58 @@ def _handle_member_event(data, repo_full_name):
'github_id': str(member.get('id', '')),
'role': 'member'
})
repo_doc.save(ignore_permissions=True)

elif action == 'removed':
# Remove member from repository
for mem in repo_doc.members_table:
if mem.github_username == username:
repo_doc.remove(mem)
break
repo_doc.save(ignore_permissions=True)

if hasattr(repo_doc, 'members_table') and repo_doc.members_table:
for mem in repo_doc.members_table[:]: # Create a copy to iterate over
if mem.github_username == username:
repo_doc.remove(mem)
break

repo_doc.flags.ignore_permissions = True
repo_doc.save()
frappe.db.commit()

except Exception as e:
frappe.log_error(f'Error handling member event: {str(e)}', 'GitHub Member Webhook')
frappe.log_error(f'Error handling member event: {frappe.get_traceback()}', 'GitHub Member Webhook')

def _handle_repository_event(data, repo_full_name):
"""Handle GitHub repository webhook events"""
try:
action = data.get('action')
repository = data.get('repository', {})

if not repository:
frappe.log_error('No repository data in webhook payload', 'GitHub Repository Webhook')
return

if action in ['edited', 'renamed']:
repo_doc = frappe.get_doc('Repository', {'full_name': repo_full_name})
# Get repository document
repo_filters = {'full_name': repo_full_name}
if not frappe.db.exists('Repository', repo_filters):
frappe.log_error(f'Repository {repo_full_name} not found', 'GitHub Repository Webhook')
return

repo_doc = frappe.get_doc('Repository', repo_filters)

# Update basic repository information
repo_doc.visibility = 'Private' if repository.get('private') else 'Public'
repo_doc.default_branch = repository.get('default_branch', 'main')
repo_doc.description = repository.get('description', '')

# Handle repository rename
if action == 'renamed' and repository.get('full_name') != repo_full_name:
repo_doc.full_name = repository.get('full_name')
repo_doc.repo_name = repository.get('name')
repo_doc.repo_owner = repository.get('owner', {}).get('login', '')
repo_doc.url = repository.get('html_url', '')
if action == 'renamed':
new_full_name = repository.get('full_name')
if new_full_name and new_full_name != repo_full_name:
repo_doc.full_name = new_full_name
repo_doc.repo_name = repository.get('name')
repo_doc.repo_owner = repository.get('owner', {}).get('login', '')
repo_doc.url = repository.get('html_url', '')

repo_doc.save(ignore_permissions=True)
repo_doc.flags.ignore_permissions = True
repo_doc.save()
frappe.db.commit()

except Exception as e:
frappe.log_error(f'Error handling repository event: {str(e)}', 'GitHub Repository Webhook')
frappe.log_error(f'Error handling repository event: {frappe.get_traceback()}', 'GitHub Repository Webhook')
Loading