From 307c15aaa04efc6df1360a43e36cfa184c212b51 Mon Sep 17 00:00:00 2001 From: long2ice Date: Mon, 19 May 2025 15:57:49 +0800 Subject: [PATCH] feat: add GitLab work report generation command and documentation --- community/github/branch/new/command.py | 4 +- .../github/commit/{__init__py => __init__.py} | 0 community/github/commit/main.py | 4 +- community/github/config/command.py | 2 +- community/github/issue/list_tasks/command.py | 4 +- community/github/issue/new/command.py | 4 +- .../github/issue/new/from_task/command.py | 4 +- .../github/issue/update_tasks/command.py | 4 +- community/github/pr/new/command.py | 3 +- community/github/pr/update/command.py | 8 +- community/gitlab/branch/new/command.py | 3 +- community/gitlab/commit/main.py | 22 ++-- community/gitlab/common_util.py | 78 -------------- community/gitlab/config/command.py | 100 +++--------------- community/gitlab/git_api.py | 82 ++++++++------ community/gitlab/issue/list_tasks/command.py | 3 +- community/gitlab/issue/new/command.py | 3 +- .../gitlab/issue/new/from_task/command.py | 3 +- .../gitlab/issue/update_tasks/command.py | 3 +- community/gitlab/pr/new/command.py | 7 +- community/gitlab/pr/update/command.py | 11 +- community/gitlab/work_report/README.md | 47 ++++++++ community/gitlab/work_report/README.zh.md | 47 ++++++++ community/gitlab/work_report/command.py | 89 ++++++++++++++++ community/gitlab/work_report/command.yml | 7 ++ community/gitlab/work_report/template.md | 1 + lib/chatmark/form.py | 6 +- .../github => lib/workflow}/common_util.py | 0 lib/workflow/config.py | 32 ++++++ lib/workflow/decorators.py | 5 +- requirements-dev.txt | 2 +- 31 files changed, 350 insertions(+), 238 deletions(-) rename community/github/commit/{__init__py => __init__.py} (100%) delete mode 100644 community/gitlab/common_util.py create mode 100644 community/gitlab/work_report/README.md create mode 100644 community/gitlab/work_report/README.zh.md create mode 100755 community/gitlab/work_report/command.py create mode 100644 community/gitlab/work_report/command.yml create mode 100644 community/gitlab/work_report/template.md rename {community/github => lib/workflow}/common_util.py (100%) create mode 100644 lib/workflow/config.py diff --git a/community/github/branch/new/command.py b/community/github/branch/new/command.py index 234c4a4..d224755 100644 --- a/community/github/branch/new/command.py +++ b/community/github/branch/new/command.py @@ -7,7 +7,6 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, ui_edit # noqa: E402 from git_api import ( # noqa: E402 check_git_installed, create_and_checkout_branch, @@ -16,6 +15,8 @@ save_last_base_branch, ) +from lib.workflow.common_util import assert_exit, ui_edit # noqa: E402 + # Function to generate a random branch name PROMPT = ( "Give me 5 different git branch names, " @@ -94,3 +95,4 @@ def main(): if __name__ == "__main__": main() + main() diff --git a/community/github/commit/__init__py b/community/github/commit/__init__.py similarity index 100% rename from community/github/commit/__init__py rename to community/github/commit/__init__.py diff --git a/community/github/commit/main.py b/community/github/commit/main.py index affd22f..6568865 100644 --- a/community/github/commit/main.py +++ b/community/github/commit/main.py @@ -11,7 +11,6 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) -from common_util import assert_exit # noqa: E402 from git_api import ( get_github_repo, get_github_repo_issues, @@ -21,6 +20,8 @@ subprocess_run, ) +from lib.workflow.common_util import assert_exit # noqa: E402 + diff_too_large_message_en = ( "Commit failed. The modified content is too long " "and exceeds the model's length limit. " @@ -537,3 +538,4 @@ def main(): if __name__ == "__main__": main() + main() diff --git a/community/github/config/command.py b/community/github/config/command.py index 6150cc5..f9db52b 100644 --- a/community/github/config/command.py +++ b/community/github/config/command.py @@ -4,7 +4,7 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) -from common_util import editor # noqa: E402 +from lib.workflow.common_util import editor # noqa: E402 def read_issue_url(): diff --git a/community/github/issue/list_tasks/command.py b/community/github/issue/list_tasks/command.py index 9d14afb..8be5404 100644 --- a/community/github/issue/list_tasks/command.py +++ b/community/github/issue/list_tasks/command.py @@ -6,9 +6,10 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, editor # noqa: E402 from git_api import create_issue # noqa: E402 +from lib.workflow.common_util import assert_exit, editor # noqa: E402 + # Function to generate issue title and body using LLM PROMPT = ( "Based on the following description, " @@ -52,3 +53,4 @@ def main(): if __name__ == "__main__": main() + main() diff --git a/community/github/issue/new/command.py b/community/github/issue/new/command.py index d100633..3fc8f1f 100644 --- a/community/github/issue/new/command.py +++ b/community/github/issue/new/command.py @@ -4,10 +4,11 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, editor # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import create_issue # noqa: E402 +from lib.workflow.common_util import assert_exit, editor # noqa: E402 + # Function to generate issue title and body using LLM PROMPT = ( "Based on the following description, " @@ -51,3 +52,4 @@ def main(): if __name__ == "__main__": main() + main() diff --git a/community/github/issue/new/from_task/command.py b/community/github/issue/new/from_task/command.py index 7c74b15..f46af2f 100644 --- a/community/github/issue/new/from_task/command.py +++ b/community/github/issue/new/from_task/command.py @@ -5,7 +5,6 @@ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ) sys.path.append(ROOT_DIR) -from common_util import assert_exit, editor, ui_edit # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 create_issue, @@ -15,6 +14,8 @@ update_task_issue_url, ) +from lib.workflow.common_util import assert_exit, editor, ui_edit # noqa: E402 + # Function to generate issue title and body using LLM PROMPT = ( "Following is parent issue content:\n" @@ -94,3 +95,4 @@ def main(): if __name__ == "__main__": main() + main() diff --git a/community/github/issue/update_tasks/command.py b/community/github/issue/update_tasks/command.py index b1d6dbf..e4e4303 100644 --- a/community/github/issue/update_tasks/command.py +++ b/community/github/issue/update_tasks/command.py @@ -3,7 +3,6 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) -from common_util import assert_exit, editor # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 get_issue_info_by_url, @@ -12,6 +11,8 @@ update_sub_tasks, ) +from lib.workflow.common_util import assert_exit, editor # noqa: E402 + TASKS_PROMPT = ( "Following is my git issue content.\n" "{issue_data}\n\n" @@ -99,3 +100,4 @@ def main(): if __name__ == "__main__": main() + main() diff --git a/community/github/pr/new/command.py b/community/github/pr/new/command.py index d75cff5..def3514 100644 --- a/community/github/pr/new/command.py +++ b/community/github/pr/new/command.py @@ -5,7 +5,6 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, ui_edit # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 auto_push, @@ -18,6 +17,8 @@ save_last_base_branch, ) +from lib.workflow.common_util import assert_exit, ui_edit # noqa: E402 + # 从分支名称中提取issue id def extract_issue_id(branch_name): diff --git a/community/github/pr/update/command.py b/community/github/pr/update/command.py index 276c6ef..cd26783 100644 --- a/community/github/pr/update/command.py +++ b/community/github/pr/update/command.py @@ -5,10 +5,7 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, ui_edit # noqa: E402 -from devchat.llm import ( # noqa: E402 - chat_json, -) +from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 auto_push, get_commit_messages, @@ -21,6 +18,8 @@ update_pr, ) +from lib.workflow.common_util import assert_exit, ui_edit # noqa: E402 + # 从分支名称中提取issue id def extract_issue_id(branch_name): @@ -120,3 +119,4 @@ def main(): if __name__ == "__main__": main() + main() diff --git a/community/gitlab/branch/new/command.py b/community/gitlab/branch/new/command.py index 5d6a5f0..911ba90 100644 --- a/community/gitlab/branch/new/command.py +++ b/community/gitlab/branch/new/command.py @@ -7,7 +7,6 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, ui_edit # noqa: E402 from git_api import ( # noqa: E402 check_git_installed, create_and_checkout_branch, @@ -16,6 +15,8 @@ save_last_base_branch, ) +from lib.workflow.common_util import assert_exit, ui_edit # noqa: E402 + # Function to generate a random branch name PROMPT = ( "Give me 5 different git branch names, " diff --git a/community/gitlab/commit/main.py b/community/gitlab/commit/main.py index 2ede196..78b657d 100644 --- a/community/gitlab/commit/main.py +++ b/community/gitlab/commit/main.py @@ -11,16 +11,17 @@ sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) -from common_util import assert_exit # noqa: E402 from git_api import ( - get_gitlab_repo, - get_gitlab_repo_issues, - get_gitlab_username, get_issue_info, + get_repo, + get_repo_issues, + get_username, subprocess_check_output, subprocess_run, ) +from lib.workflow.common_util import assert_exit # noqa: E402 + diff_too_large_message_en = ( "Commit failed. The modified content is too long " "and exceeds the model's length limit. " @@ -428,9 +429,9 @@ def get_selected_issue_ids(): Returns: list: 用户选中的issue id列表 """ - name = get_gitlab_username() - issue_repo = get_gitlab_repo(True) - issues = get_gitlab_repo_issues(issue_repo, name) + name = get_username() + issue_repo = get_repo(True) + issues = get_repo_issues(issue_repo, name) if issues: checkbox = Checkbox( [f"#{issue['iid']}: {issue['title']}" for issue in issues], @@ -458,8 +459,7 @@ def main(): sys.exit(-1) print( - "Step 1/3: Select the changed files to include in this commit, " - "then click 'Continue'.", + "Step 1/3: Select the changed files to include in this commit, then click 'Continue'.", end="\n\n", flush=True, ) @@ -499,8 +499,8 @@ def main(): # add closes #IssueNumber in commit message from issues from user selected issue_ids = get_selected_issue_ids() if issue_ids: - issue_repo = get_gitlab_repo(True) - owner_repo = get_gitlab_repo() + issue_repo = get_repo(True) + owner_repo = get_repo() closes_issue_contents = [] for issue_id in issue_ids: closes_issue_contents.append( diff --git a/community/gitlab/common_util.py b/community/gitlab/common_util.py deleted file mode 100644 index 6e3f2fc..0000000 --- a/community/gitlab/common_util.py +++ /dev/null @@ -1,78 +0,0 @@ -import functools -import sys - -from lib.chatmark import Checkbox, Form, Radio, TextEditor - - -def create_ui_objs(ui_decls, args): - ui_objs = [] - editors = [] - for i, ui in enumerate(ui_decls): - editor = ui[0](args[i]) - if ui[1]: - # this is the title of UI object - editors.append(ui[1]) - editors.append(editor) - ui_objs.append(editor) - return ui_objs, editors - - -def edit_form(uis, args): - ui_objs, editors = create_ui_objs(uis, args) - form = Form(editors) - form.render() - - values = [] - for obj in ui_objs: - if isinstance(obj, TextEditor): - values.append(obj.new_text) - elif isinstance(obj, Radio): - values.append(obj.selection) - else: - # TODO - pass - return values - - -def editor(description): - def decorator_edit(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - uis = wrapper.uis[::-1] - return edit_form(uis, args) - - if hasattr(func, "uis"): - wrapper.uis = func.uis - else: - wrapper.uis = [] - wrapper.uis.append((TextEditor, description)) - return wrapper - - return decorator_edit - - -def ui_edit(ui_type, description): - def decorator_edit(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - uis = wrapper.uis[::-1] - return edit_form(uis, args) - - if hasattr(func, "uis"): - wrapper.uis = func.uis - else: - wrapper.uis = [] - ui_type_class = {"editor": TextEditor, "radio": Radio, "checkbox": Checkbox}[ui_type] - wrapper.uis.append((ui_type_class, description)) - return wrapper - - return decorator_edit - - -def assert_exit(condition, message, exit_code=-1): - if condition: - if exit_code == 0: - print(message, end="\n\n", flush=True) - else: - print(message, end="\n\n", file=sys.stderr, flush=True) - sys.exit(exit_code) diff --git a/community/gitlab/config/command.py b/community/gitlab/config/command.py index 8493277..ac38697 100644 --- a/community/gitlab/config/command.py +++ b/community/gitlab/config/command.py @@ -1,80 +1,11 @@ -import json import os import sys -sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) - -from common_util import editor # noqa: E402 - - -def read_issue_url(): - config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json") - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8") as f: - config_data = json.load(f) - if "git_issue_repo" in config_data: - return config_data["git_issue_repo"] - return "" - - -def save_issue_url(issue_url): - config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json") - # make dirs - os.makedirs(os.path.dirname(config_path), exist_ok=True) - - config_data = {} - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8") as f: - config_data = json.load(f) - - config_data["git_issue_repo"] = issue_url - with open(config_path, "w+", encoding="utf-8") as f: - json.dump(config_data, f, indent=4) - +from lib.workflow.config import read_config, save_config -def read_gitlab_token(): - config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json") - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8") as f: - config_data = json.load(f) - if "gitlab_token" in config_data: - return config_data["gitlab_token"] - return "" - - -def save_gitlab_token(github_token): - config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json") - - config_data = {} - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8") as f: - config_data = json.load(f) - - config_data["gitlab_token"] = github_token - with open(config_path, "w+", encoding="utf-8") as f: - json.dump(config_data, f, indent=4) - - -def read_gitlab_api_url(): - config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json") - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8") as f: - config_data = json.load(f) - if "gitlab_api_url" in config_data: - return config_data["gitlab_api_url"] - return "" - - -def save_gitlab_api_url(gitlab_api_url): - config_path = os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json") - config_data = {} - if os.path.exists(config_path): - with open(config_path, "r", encoding="utf-8") as f: - config_data = json.load(f) +sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) - config_data["gitlab_api_url"] = gitlab_api_url - with open(config_path, "w+", encoding="utf-8") as f: - json.dump(config_data, f, indent=4) +from lib.workflow.common_util import editor # noqa: E402 @editor( @@ -86,25 +17,26 @@ def save_gitlab_api_url(gitlab_api_url): "Input your gitlab API URL to access gitlab api, if not specified, default is https://gitlab.com/api/v4" ) @editor("Input your gitlab TOKEN to access gitlab api") -def edit_issue(issue_url, gitlab_api_url, gitlab_token): +@editor("Input your gitlab work report template path") +def edit_config(issue_url, gitlab_api_url, gitlab_token, template_path): pass def main(): - issue_url = read_issue_url() - gitlab_token = read_gitlab_token() - gitlab_api_url = read_gitlab_api_url() - issue_url, gitlab_api_url, gitlab_token = edit_issue(issue_url, gitlab_api_url, gitlab_token) - if issue_url: - save_issue_url(issue_url) - if gitlab_token: - save_gitlab_token(gitlab_token) - if not gitlab_api_url: - gitlab_api_url = "https://gitlab.com/api/v4" - save_gitlab_api_url(gitlab_api_url) + issue_url = read_config("git_issue_repo", is_global=True) + gitlab_token = read_config("gitlab_token", is_global=True) + gitlab_api_url = read_config("gitlab_api_url", is_global=True) + template_path = read_config("gitlab_work_report_template_path", is_global=True) + issue_url, gitlab_api_url, gitlab_token, template_path = edit_config( + issue_url, gitlab_api_url, gitlab_token, template_path + ) if not gitlab_token: print("Please specify the gitlab token to access gitlab api.") sys.exit(0) + save_config("git_issue_repo", issue_url, is_global=True) + save_config("gitlab_token", gitlab_token, is_global=True) + save_config("gitlab_api_url", gitlab_api_url, is_global=True) + save_config("gitlab_work_report_template_path", template_path, is_global=True) print("config gitlab settings successfully.") sys.exit(0) diff --git a/community/gitlab/git_api.py b/community/gitlab/git_api.py index d72af64..241386e 100644 --- a/community/gitlab/git_api.py +++ b/community/gitlab/git_api.py @@ -9,6 +9,7 @@ from lib.ide_service import IDEService from lib.workflow.call import workflow_call +from lib.workflow.config import read_config def read_gitlab_token(): @@ -115,7 +116,7 @@ def create_issue(title, description): "title": title, "description": description, } - project_id = get_gitlab_project_id() + project_id = get_project_id() issue_api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues" response = requests.post(issue_api_url, headers=headers, json=data) @@ -136,7 +137,7 @@ def update_issue_body(issue_iid, issue_body): "description": issue_body, } - project_id = get_gitlab_project_id() + project_id = get_project_id() api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_iid}" response = requests.put(api_url, headers=headers, json=data) @@ -148,7 +149,7 @@ def update_issue_body(issue_iid, issue_body): return None -def get_gitlab_project_id(): +def get_project_id(): try: result = subprocess_check_output( ["git", "remote", "get-url", "origin"], stderr=subprocess.STDOUT @@ -175,7 +176,7 @@ def get_gitlab_project_id(): print(f"Error executing git command: {e}", file=sys.stderr) return None except Exception as e: - print(f"Error in get_gitlab_project_id: {e}", file=sys.stderr) + print(f"Error in get_project_id: {e}", file=sys.stderr) return None @@ -280,33 +281,12 @@ def read_issue_by_url(issue_url): return None -def get_gitlab_repo(issue_repo=False): - try: - config_path = os.path.join(os.getcwd(), ".chat", ".workflow_config.json") - if os.path.exists(config_path) and issue_repo: - with open(config_path, "r", encoding="utf-8") as f: - config_data = json.load(f) - - if "git_issue_repo" in config_data: - issue_repo = requests.utils.quote(config_data["git_issue_repo"], safe="") - print( - "current issue repo:", - config_data["git_issue_repo"], - end="\n\n", - file=sys.stderr, - flush=True, - ) - return config_data["git_issue_repo"] - - return get_gitlab_project_id() - except subprocess.CalledProcessError as e: - print(e) - # 如果发生错误,打印错误信息 - return None - except FileNotFoundError: - # 如果未找到git命令,可能是没有安装git或者不在PATH中 - print("==> File not found...") - return None +def get_repo(issue_repo=False): + if issue_repo: + git_issue_repo = read_config("git_issue_repo", is_global=False) + if git_issue_repo: + return git_issue_repo + return get_project_id() # 获取当前分支名称 @@ -360,7 +340,7 @@ def get_parent_branch(): def get_issue_info(issue_id): # 获取 GitLab 项目 ID - project_id = get_gitlab_repo() + project_id = get_repo() # 构造 GitLab API 端点 URL api_url = f"{GITLAB_API_URL}/projects/{project_id}/issues/{issue_id}" @@ -605,21 +585,33 @@ def save_config_item(config_path, item, value): save_config_item(project_config_path, "last_base_branch", base_branch) -def get_gitlab_repo_issues(repo: str, assignee_username: str, state: str = "opened"): +def get_repo_issues( + repo: str, + assignee_username: str, + state: str = "opened", + created_after=None, + created_before=None, +): url = f"{GITLAB_API_URL}/projects/{repo}/issues" params = { "state": state, "assignee_username": assignee_username, } + if created_after: + params["created_after"] = created_after + if created_before: + params["created_before"] = created_before headers = { "Private-Token": GITLAB_ACCESS_TOKEN, "Content-Type": "application/json", } + print(f"url: {url}", file=sys.stderr) + print(f"params: {params}", file=sys.stderr) response = requests.get(url, headers=headers, params=params) return response.json() -def get_gitlab_username(): +def get_username(): url = f"{GITLAB_API_URL}/user" headers = { "Private-Token": GITLAB_ACCESS_TOKEN, @@ -627,3 +619,25 @@ def get_gitlab_username(): } response = requests.get(url, headers=headers) return response.json()["username"] + + +def get_commit_author(): + cmd = ["git", "config", "user.name"] + return subprocess_check_output(cmd).decode("utf-8").strip() + + +def get_repo_commits(repo: str, author=None, since=None, until=None): + url = f"{GITLAB_API_URL}/projects/{repo}/repository/commits" + params = {} + if author: + params["author"] = author + if since: + params["since"] = since + if until: + params["until"] = until + headers = { + "Private-Token": GITLAB_ACCESS_TOKEN, + "Content-Type": "application/json", + } + response = requests.get(url, headers=headers, params=params) + return response.json() diff --git a/community/gitlab/issue/list_tasks/command.py b/community/gitlab/issue/list_tasks/command.py index 125066a..4e9fbc1 100644 --- a/community/gitlab/issue/list_tasks/command.py +++ b/community/gitlab/issue/list_tasks/command.py @@ -6,9 +6,10 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, editor # noqa: E402 from git_api import create_issue # noqa: E402 +from lib.workflow.common_util import assert_exit, editor # noqa: E402 + # Function to generate issue title and description using LLM PROMPT = ( "Based on the following description, " diff --git a/community/gitlab/issue/new/command.py b/community/gitlab/issue/new/command.py index 4b77e73..bc8fcaf 100644 --- a/community/gitlab/issue/new/command.py +++ b/community/gitlab/issue/new/command.py @@ -4,10 +4,11 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, editor # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import create_issue # noqa: E402 +from lib.workflow.common_util import assert_exit, editor # noqa: E402 + # Function to generate issue title and description using LLM PROMPT = ( "Based on the following description, " diff --git a/community/gitlab/issue/new/from_task/command.py b/community/gitlab/issue/new/from_task/command.py index e78b7db..4fe1897 100644 --- a/community/gitlab/issue/new/from_task/command.py +++ b/community/gitlab/issue/new/from_task/command.py @@ -5,7 +5,6 @@ os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) ) sys.path.append(ROOT_DIR) -from common_util import assert_exit, editor, ui_edit # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 create_issue, @@ -15,6 +14,8 @@ update_task_issue_url, ) +from lib.workflow.common_util import assert_exit, editor, ui_edit # noqa: E402 + # Function to generate issue title and description using LLM PROMPT = ( "Following is parent issue content:\n" diff --git a/community/gitlab/issue/update_tasks/command.py b/community/gitlab/issue/update_tasks/command.py index 3ff1b6c..7358553 100644 --- a/community/gitlab/issue/update_tasks/command.py +++ b/community/gitlab/issue/update_tasks/command.py @@ -4,7 +4,6 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, editor # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 get_issue_info_by_url, @@ -13,6 +12,8 @@ update_sub_tasks, ) +from lib.workflow.common_util import assert_exit, editor # noqa: E402 + TASKS_PROMPT = ( "Following is my git issue content.\n" "{issue_data}\n\n" diff --git a/community/gitlab/pr/new/command.py b/community/gitlab/pr/new/command.py index d26c9f5..c6c2e17 100644 --- a/community/gitlab/pr/new/command.py +++ b/community/gitlab/pr/new/command.py @@ -5,19 +5,20 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, ui_edit # noqa: E402 from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 auto_push, create_pull_request, get_commit_messages, get_current_branch, - get_gitlab_repo, get_issue_info, get_last_base_branch, + get_repo, save_last_base_branch, ) +from lib.workflow.common_util import assert_exit, ui_edit # noqa: E402 + # 从分支名称中提取issue id def extract_issue_id(branch_name): @@ -87,7 +88,7 @@ def main(): base_branch = base_branch[0] save_last_base_branch(base_branch) - repo_name = get_gitlab_repo() + repo_name = get_repo() branch_name = get_current_branch() issue_id = extract_issue_id(branch_name) diff --git a/community/gitlab/pr/update/command.py b/community/gitlab/pr/update/command.py index cb17527..5bf5fbb 100644 --- a/community/gitlab/pr/update/command.py +++ b/community/gitlab/pr/update/command.py @@ -5,22 +5,21 @@ ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) sys.path.append(ROOT_DIR) -from common_util import assert_exit, ui_edit # noqa: E402 -from devchat.llm import ( # noqa: E402 - chat_json, -) +from devchat.llm import chat_json # noqa: E402 from git_api import ( # noqa: E402 auto_push, get_commit_messages, get_current_branch, - get_gitlab_repo, get_issue_info, get_last_base_branch, get_recently_pr, + get_repo, save_last_base_branch, update_pr, ) +from lib.workflow.common_util import assert_exit, ui_edit # noqa: E402 + # 从分支名称中提取issue id def extract_issue_id(branch_name): @@ -87,7 +86,7 @@ def main(): base_branch = base_branch[0] save_last_base_branch(base_branch) - repo_name = get_gitlab_repo() + repo_name = get_repo() branch_name = get_current_branch() issue_id = extract_issue_id(branch_name) diff --git a/community/gitlab/work_report/README.md b/community/gitlab/work_report/README.md new file mode 100644 index 0000000..8be70aa --- /dev/null +++ b/community/gitlab/work_report/README.md @@ -0,0 +1,47 @@ +# /gitlab.work_report + +Generate user weekly report based on gitlab issue and commit information + +## Purpose + +- Automatically generate work reports based on GitLab activities +- Track and summarize issues and commits within a specified time period +- Provide a structured format for work reporting + +## Usage Method + +1. Execute the command with date range: + + ```shell + /gitlab.work_report + ``` + + Example: `/gitlab.work_report 2024-03-01 2024-03-07` + +2. Or execute without parameters to generate yesterday's report: + + ```shell + /gitlab.work_report + ``` + +## Features + +- Generates reports based on: + - Issues created/updated within the specified time period + - Commits made within the specified time period +- Uses a customizable template for report formatting +- Supports both English and Chinese documentation +- Automatically detects user's GitLab username and repository information + +## Report Content + +The report includes: + +- Time period covered +- List of issues worked on +- List of commits made +- Formatted according to the template specified in configuration + +## Configuration + +The report template can be customized by setting the `gitlab_work_report_template_path` in the global configuration. diff --git a/community/gitlab/work_report/README.zh.md b/community/gitlab/work_report/README.zh.md new file mode 100644 index 0000000..3a110ba --- /dev/null +++ b/community/gitlab/work_report/README.zh.md @@ -0,0 +1,47 @@ +# /gitlab.work_report + +根据用户 gitlab issue 信息和 commit 信息生成用户周日报 + +## 功能目的 + +- 基于 GitLab 活动自动生成工作报告 +- 追踪和总结指定时间段内的 issues 和 commits +- 提供结构化的工作报告格式 + +## 使用方法 + +1. 带日期范围执行命令: + + ```shell + /gitlab.work_report <开始日期> <结束日期> + ``` + + 示例:`/gitlab.work_report 2024-03-01 2024-03-07` + +2. 不带参数执行命令(生成昨天的报告): + + ```shell + /gitlab.work_report + ``` + +## 功能特点 + +- 报告生成基于: + - 指定时间段内创建/更新的 issues + - 指定时间段内的 commits +- 使用可自定义的报告模板 +- 支持中英文文档 +- 自动检测用户的 GitLab 用户名和仓库信息 + +## 报告内容 + +报告包含: + +- 报告时间范围 +- 处理的 issues 列表 +- 提交的 commits 列表 +- 根据配置的模板格式化输出 + +## 配置说明 + +可以通过全局配置中的 `gitlab_work_report_template_path` 来自定义报告模板。 diff --git a/community/gitlab/work_report/command.py b/community/gitlab/work_report/command.py new file mode 100755 index 0000000..2506582 --- /dev/null +++ b/community/gitlab/work_report/command.py @@ -0,0 +1,89 @@ +import os +import sys +from datetime import datetime, timedelta + +from devchat.llm import chat + +from community.gitlab.git_api import ( + get_commit_author, + get_repo, + get_repo_commits, + get_repo_issues, + get_username, +) +from lib.workflow.config import read_config + +PROMPT = """ +我希望你根据以下信息生成一份从 {start_time} 到 {end_time} 的 Gitlab 工作报告。 + +问题列表: + +{issues} + + +提交列表: + +{commits} + + +请参考以下模板内容: + +""" + + +def get_template(): + template_path = read_config( + "gitlab_work_report_template_path", + is_global=True, + default=os.path.join(os.path.dirname(__file__), "template.md"), + ) + with open(template_path, "r", encoding="utf-8") as f: + return f.read() + + +def get_issues(start_time, end_time): + name = get_username() + issue_repo = get_repo(True) + issues = get_repo_issues( + issue_repo, name, state=None, created_after=start_time, created_before=end_time + ) + return issues + + +def get_commits(start_time, end_time): + name = get_commit_author() + issue_repo = get_repo(False) + commits = get_repo_commits(issue_repo, author=name, since=start_time, until=end_time) + return commits + + +@chat(prompt=PROMPT, stream_out=True) +def generate_work_report(start_time, end_time, issues, commits, template): + pass + + +def main(): + arg = sys.argv[1] + args = arg.split(" ") + if len(args) == 3: + start_time = args[1] + end_time = args[2] + else: + end_time = datetime.now().strftime("%Y-%m-%d") + start_time = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d") + issues = get_issues(start_time, end_time) + commits = get_commits(start_time, end_time) + template = get_template() + generate_work_report( + start_time=start_time, + end_time=end_time, + issues=issues, + commits=commits, + template=template, + ) + + +if __name__ == "__main__": + main() diff --git a/community/gitlab/work_report/command.yml b/community/gitlab/work_report/command.yml new file mode 100644 index 0000000..7118268 --- /dev/null +++ b/community/gitlab/work_report/command.yml @@ -0,0 +1,7 @@ +description: Generate work report based on the user's issue and commit information +input: optional +help: + en: README.md + zh: README.zh.md +steps: + - run: $devchat_python $command_path/command.py "$input" diff --git a/community/gitlab/work_report/template.md b/community/gitlab/work_report/template.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/community/gitlab/work_report/template.md @@ -0,0 +1 @@ + diff --git a/lib/chatmark/form.py b/lib/chatmark/form.py index facbf62..48cedc4 100644 --- a/lib/chatmark/form.py +++ b/lib/chatmark/form.py @@ -22,9 +22,9 @@ def __init__( components: components in the form, can be widgets (except Button) or strings title: title of the form """ - assert ( - any(isinstance(c, Button) for c in components) is False - ), "Button is not allowed in Form" + assert any(isinstance(c, Button) for c in components) is False, ( + "Button is not allowed in Form" + ) self._components = components self._title = title diff --git a/community/github/common_util.py b/lib/workflow/common_util.py similarity index 100% rename from community/github/common_util.py rename to lib/workflow/common_util.py diff --git a/lib/workflow/config.py b/lib/workflow/config.py new file mode 100644 index 0000000..02e2924 --- /dev/null +++ b/lib/workflow/config.py @@ -0,0 +1,32 @@ +import json +import os + + +def get_config_path(is_global: bool = False): + if is_global: + return os.path.join(os.path.expanduser("~/.chat"), ".workflow_config.json") + else: + return os.path.join(os.getcwd(), ".chat", ".workflow_config.json") + + +def read_config(key: str, is_global: bool = False, default: str = ""): + config_path = get_config_path(is_global) + + config_data = {} + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8") as f: + config_data = json.load(f) + return config_data.get(key) or default + + +def save_config(key: str, value: str, is_global: bool = False): + config_path = get_config_path(is_global) + + config_data = {} + if os.path.exists(config_path): + with open(config_path, "r", encoding="utf-8") as f: + config_data = json.load(f) + + config_data[key] = value + with open(config_path, "w+", encoding="utf-8") as f: + json.dump(config_data, f, indent=4) diff --git a/lib/workflow/decorators.py b/lib/workflow/decorators.py index 1d56ece..58ce64b 100644 --- a/lib/workflow/decorators.py +++ b/lib/workflow/decorators.py @@ -2,6 +2,7 @@ import json import os import sys +from typing import Callable, Optional from lib.ide_service import IDEService @@ -51,7 +52,7 @@ def wrapper(*args, **kwargs): return decorator -def check_input(description: str): +def check_input(description: str, callback: Optional[Callable] = None): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): @@ -59,6 +60,8 @@ def wrapper(*args, **kwargs): if len(arg) == 0: print(description, file=sys.stderr) sys.exit(1) + if callback: + return callback(arg) return func(arg, *args, **kwargs) return wrapper diff --git a/requirements-dev.txt b/requirements-dev.txt index d2aa615..af3ee57 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1 +1 @@ -ruff~=0.1.7 +ruff