From 0d782144cf5020b1699503f218012ab08266a8f2 Mon Sep 17 00:00:00 2001 From: Yankyyyy Date: Tue, 16 Sep 2025 06:07:28 +0000 Subject: [PATCH] refactor: task and project doctype and it's related github api updated --- erpnext_github_integration/github_api.py | 62 ++++++++++++++----- .../patches/after_install.py | 10 ++- .../public/js/project_client.js | 2 +- .../public/js/task_client.js | 58 +++++++++++++---- 4 files changed, 102 insertions(+), 30 deletions(-) diff --git a/erpnext_github_integration/github_api.py b/erpnext_github_integration/github_api.py index d92f6a8..07f4693 100644 --- a/erpnext_github_integration/github_api.py +++ b/erpnext_github_integration/github_api.py @@ -236,29 +236,61 @@ def assign_issue(repo_full_name, issue_number, assignees): token = settings.get_password('personal_access_token') if not token: frappe.throw(_('GitHub Personal Access Token not configured in GitHub Settings')) + + # normalize assignees input if isinstance(assignees, str): try: assignees = json.loads(assignees) except Exception: assignees = [a.strip() for a in assignees.split(',') if a.strip()] - payload = {'assignees': assignees} + + github_usernames = [] + + try: + local = frappe.get_doc('Repository Issue', { + 'repository': repo_full_name, + 'issue_number': int(issue_number) + }) + + # Reset assignees table + local.set('assignees_table', []) + + for user_id in assignees: + # map ERPNext user → GitHub username + gh_username = frappe.db.get_value("User", user_id, "github_username") + + if not gh_username: + frappe.log_error( + f"User {user_id} has no GitHub username set", + "GitHub Assignee Mapping" + ) + continue # skip if no GitHub username + + github_usernames.append(gh_username) + + # store ERPNext user (email / id) in local doc + local.append('assignees_table', { + 'issue': local.name, + 'user': user_id + }) + + local.save(ignore_permissions=True) + + except Exception: + frappe.log_error(frappe.get_traceback(), "Failed to update local Repository Issue assignees") + + # send to GitHub (GitHub API needs GitHub usernames) + payload = {'assignees': github_usernames} try: - resp = github_request('PATCH', f"/repos/{repo_full_name}/issues/{issue_number}", token, data=payload) + resp = github_request( + 'PATCH', + f"/repos/{repo_full_name}/issues/{issue_number}", + token, + data=payload + ) except Exception as e: frappe.throw(_('GitHub assign issue failed: {0}').format(str(e))) - if resp: - try: - local = frappe.get_doc('Repository Issue', {'repository': repo_full_name, 'issue_number': int(issue_number)}) - # Update assignees table - local.set('assignees_table', []) - for assignee in assignees: - local.append('assignees_table', { - 'user': assignee - }) - local.save(ignore_permissions=True) - except Exception: - pass - return resp + return resp @frappe.whitelist() diff --git a/erpnext_github_integration/patches/after_install.py b/erpnext_github_integration/patches/after_install.py index 2d9cdb2..6e91b76 100644 --- a/erpnext_github_integration/patches/after_install.py +++ b/erpnext_github_integration/patches/after_install.py @@ -33,11 +33,19 @@ def create_custom_fields_and_scripts(): insert_after='project', description='GitHub repository this task belongs to' ), + dict( + fieldname='github_issue_doc', + label='GitHub Issue Doc', + fieldtype='Link', + options='Repository Issue', + insert_after='github_repo', + description='GitHub issue doc if this task is linked to an issue' + ), dict( fieldname='github_issue_number', label='GitHub Issue Number', fieldtype='Int', - insert_after='github_repo', + insert_after='github_issue_doc', description='GitHub issue number if this task is linked to an issue' ), dict( diff --git a/erpnext_github_integration/public/js/project_client.js b/erpnext_github_integration/public/js/project_client.js index 8bf8148..df419b2 100644 --- a/erpnext_github_integration/public/js/project_client.js +++ b/erpnext_github_integration/public/js/project_client.js @@ -23,7 +23,7 @@ frappe.ui.form.on('Project', { } frappe.call({ method: 'erpnext_github_integration.github_api.sync_repo', - args: {repo_full_name: repo}, + args: {repository: repo}, callback: function(r) { frappe.msgprint(__('Repository sync completed.')); frm.reload_doc(); diff --git a/erpnext_github_integration/public/js/task_client.js b/erpnext_github_integration/public/js/task_client.js index 8b457e7..0741c33 100644 --- a/erpnext_github_integration/public/js/task_client.js +++ b/erpnext_github_integration/public/js/task_client.js @@ -6,18 +6,26 @@ frappe.ui.form.on('Task', { frappe.msgprint(__('Please set the GitHub Repo (link to Repository) in the Task field "GitHub Repo"')); return; } + frappe.prompt([ {'fieldname':'title','fieldtype':'Data','label':'Issue Title','reqd':1}, {'fieldname':'body','fieldtype':'Text','label':'Issue Body'} ], function(values){ frappe.call({ method: 'erpnext_github_integration.github_api.create_issue', - args: {repo_full_name: repo, title: values.title, body: values.body}, + args: {repository: repo, title: values.title, body: values.body}, callback: function(r) { if (r.message) { - frappe.msgprint(__('Created issue: {0}').format(r.message.issue.get('html_url') || r.message.issue.get('url') || '')); - frm.set_value('github_issue_number', r.message.issue.get('number')); + let issue = r.message.issue; + let url = issue.html_url || issue.url; + + frappe.msgprint(__('Created issue: {0}').format(url)); + + // Save both: local doc link & GitHub issue number + frm.set_value('github_issue_doc', r.message.local_doc); + frm.set_value('github_issue_number', issue.number); frm.set_value('github_repo', repo); + frm.save(); } } @@ -55,21 +63,45 @@ frappe.ui.form.on('Task', { frm.add_custom_button(__('Assign Issue'), function() { let repo = frm.doc.github_repo; let issue_no = frm.doc.github_issue_number; + if (!repo || !issue_no) { frappe.msgprint(__('This Task must have GitHub Repo and GitHub Issue Number set.')); return; } - frappe.prompt([ - {'fieldname':'assignees','fieldtype':'Data','label':'Assignees (comma separated GitHub usernames)','reqd':1} - ], function(values){ - frappe.call({ - method: 'erpnext_github_integration.github_api.assign_issue', - args: {repo_full_name: repo, issue_number: issue_no, assignees: values.assignees}, - callback: function(r) { - frappe.msgprint(__('Assigned issue {0}').format(issue_no)); + + // fetch ERPNext users with github_username set + frappe.db.get_list('User', { + fields: ['name', 'full_name', 'github_username'], + filters: { enabled: 1 }, + limit: 100 + }).then(users => { + let user_options = users.map(u => ({ + value: u.name, + label: `${u.full_name || u.name} (${u.github_username || 'no GitHub'})` + })); + + frappe.prompt([ + { + fieldname: 'assignees', + fieldtype: 'MultiSelectPills', + label: 'Assign to Users', + options: user_options, + reqd: 1 } - }); - }, __('Assign Issue')); + ], function(values) { + frappe.call({ + method: 'erpnext_github_integration.github_api.assign_issue', + args: { + repo_full_name: repo, + issue_number: issue_no, + assignees: values.assignees + }, + callback: function(r) { + frappe.msgprint(__('Assigned issue {0}', [issue_no])); + } + }); + }, __('Assign Issue')); + }); }); frm.add_custom_button(__('Add PR Reviewer'), function() {