From 240db0f44661088f69537e75a9488e84914ee842 Mon Sep 17 00:00:00 2001 From: alstr Date: Fri, 28 Jun 2024 13:56:27 +0100 Subject: [PATCH] Remove support for classic projects Closes #187 Closes #149 --- .github/workflows/todo.yml | 2 - README.md | 44 +------------ action.yml | 9 --- main.py | 122 +------------------------------------ 4 files changed, 3 insertions(+), 174 deletions(-) diff --git a/.github/workflows/todo.yml b/.github/workflows/todo.yml index 848a2ea..ebcc1c2 100644 --- a/.github/workflows/todo.yml +++ b/.github/workflows/todo.yml @@ -21,5 +21,3 @@ jobs: ${{ inputs.MANUAL_COMMIT_REF }} MANUAL_BASE_REF: ${{ inputs.MANUAL_BASE_REF }} - with: - PROJECTS_SECRET: ${{ secrets.PROJECTS_SECRET }} diff --git a/README.md b/README.md index 22c0499..043fb65 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,7 @@ Action supports: * Multiple, customizable comments identifiers (FIXME, etc.), * Configurable auto-labeling, * Assignees, -* Milestones, -* Projects (classic). +* Milestones. `todo-to-issue` works with almost any programming language. @@ -96,42 +95,6 @@ Milestone `ID` to assign to the issue: Only a single milestone can be specified and it must already exist. -### Projects - -_Please note, the action currently only supports classic user and organisation projects, and not 'new' projects._ - -With some additional setup, you can assign the created issues a status (column) within user or organisation projects. - -By default, the action cannot access your projects. To enable it, you must: - -* [Create a Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token), -* [Create an encrypted secret in your repo settings](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository), - with the value set to the Personal Access Token, -* Assign the secret in the workflow file like `PROJECTS_SECRET: ${{ secrets.PROJECTS_SECRET }}`. _Do not enter the raw - secret_. - -Projects are identified by their `full project name and issue status` (column) reference with -the `/project name/status name` syntax. - -* To assign to a _user project_, use the `user projects:` option. -* To assign to an _organisation project_, use `org projects:` option. - -```python - def hello_world(): - # TODO Come up with a more imaginative greeting - # Everyone uses hello world and it's boring. - # user projects: alstr/Test User Project/To Do - # org projects: alstrorg/Test Org Project/To Do - print('Hello world!') -``` - -You can assign issues to multiple projects separating them with commas, -i.e. `user projects: alstr/Test User Project 1/To Do, alstr/Test User Project 2/Tasks`. - -You can also specify `default projects` in the same way by defining `USER_PROJECTS` or `ORG_PROJECTS` in your workflow -file. -These will be applied automatically to every issue, but will be overrode by any specified within the TODO. - ## Supported Languages - ABAP @@ -229,10 +192,7 @@ The workflow file takes the following optional inputs: | LABEL | False | The label that will be used to identify TODO comments (deprecated) | | COMMENT_MARKER | False | The marker used to signify a line comment in your code (deprecated) | | CLOSE_ISSUES | False | Optional input that specifies whether to attempt to close an issue when a TODO is removed | -| AUTO_P | False | For multiline TODOs, format each line as a new paragraph when creating the issue | -| PROJECTS_SECRET | False | Encrypted secret corresponding to your personal access token (do not enter the actual secret) | -| USER_PROJECTS | False | Default user projects | -| ORG_PROJECTS | False | Default organization projects | +| AUTO_P | False | For multiline TODOs, format each line as a new paragraph when creating the issue | | | IGNORE | False | A collection of comma-delimited regular expressions that match files that should be ignored when searching for TODOs | | AUTO_ASSIGN | False | Automatically assign new issues to the user who triggered the action | | ACTOR | False | The username of the person who triggered the action | diff --git a/action.yml b/action.yml index 8c747a1..051629f 100644 --- a/action.yml +++ b/action.yml @@ -46,15 +46,6 @@ inputs: description: 'For multiline TODOs, format each line as a new paragraph when creating the issue' required: false default: true - PROJECTS_SECRET: - description: 'Encrypted secret corresponding to your personal access token (do not enter the actual secret)' - required: false - USER_PROJECTS: - description: 'Default user projects' - required: false - ORG_PROJECTS: - description: 'Default organisation projects' - required: false IGNORE: description: 'A collection of comma-delimited regular expression that matches files that should be ignored when searching for TODOs' required: false diff --git a/main.py b/main.py index 2abd73a..77a105e 100644 --- a/main.py +++ b/main.py @@ -23,14 +23,12 @@ class LineStatus(Enum): class Issue(object): """Basic Issue model for collecting the necessary info to send to GitHub.""" - def __init__(self, title, labels, assignees, milestone, user_projects, org_projects, body, hunk, file_name, + def __init__(self, title, labels, assignees, milestone, body, hunk, file_name, start_line, markdown_language, status, identifier): self.title = title self.labels = labels self.assignees = assignees self.milestone = milestone - self.user_projects = user_projects - self.org_projects = org_projects self.body = body self.hunk = hunk self.file_name = file_name @@ -167,16 +165,6 @@ class GitHubClient(object): new_issue_request = requests.post(url=self.issues_url, headers=self.issue_headers, data=json.dumps(new_issue_body)) - # Check if we should assign this issue to any projects. - if new_issue_request.status_code == 201 and (len(issue.user_projects) > 0 or len(issue.org_projects) > 0): - issue_json = new_issue_request.json() - issue_id = issue_json['id'] - - if len(issue.user_projects) > 0: - self.add_issue_to_projects(issue_id, issue.user_projects, 'user') - if len(issue.org_projects) > 0: - self.add_issue_to_projects(issue_id, issue.org_projects, 'org') - return new_issue_request.status_code def close_issue(self, issue): @@ -205,81 +193,6 @@ class GitHubClient(object): return update_issue_request.status_code return None - def add_issue_to_projects(self, issue_id, projects, projects_type): - """Attempt to add this issue to the specified user or organisation projects.""" - projects_secret = os.getenv('INPUT_PROJECTS_SECRET', None) - if not projects_secret: - print('You need to create and set PROJECTS_SECRET to use projects') - return - projects_headers = { - 'Accept': 'application/vnd.github.inertia-preview+json', - 'Authorization': f'token {projects_secret}' - } - - # Loop through all the projects that we should assign this issue to. - for i, project in enumerate(projects): - print(f'Adding issue to {projects_type} project {i + 1} of {len(projects)}') - project = project.replace(' / ', '/') - try: - entity_name, project_name, column_name = project.split('/') - except ValueError: - print('Invalid project syntax') - continue - entity_name = entity_name.strip() - project_name = project_name.strip() - column_name = column_name.strip() - - if projects_type == 'user': - projects_url = f'{self.base_url}users/{entity_name}/projects' - elif projects_type == 'org': - projects_url = f'{self.base_url}orgs/{entity_name}/projects' - else: - return - - # We need to use the project name to get its ID. - projects_request = requests.get(url=projects_url, headers=projects_headers) - if projects_request.status_code == 200: - projects_json = projects_request.json() - for project_dict in projects_json: - if project_dict['name'].lower() == project_name.lower(): - project_id = project_dict['id'] - break - else: - print('Project does not exist, skipping') - continue - else: - print('An error occurred, skipping') - continue - - # Use the project ID and column name to get the column ID. - columns_url = f'{self.base_url}projects/{project_id}/columns' - columns_request = requests.get(url=columns_url, headers=projects_headers) - if columns_request.status_code == 200: - columns_json = columns_request.json() - for column_dict in columns_json: - if column_dict['name'].lower() == column_name.lower(): - column_id = column_dict['id'] - break - else: - print('Column does not exist, skipping') - continue - else: - print('An error occurred, skipping') - continue - - # Use the column ID to assign the issue to the project. - new_card_url = f'{self.base_url}projects/columns/{column_id}/cards' - new_card_body = { - 'content_id': int(issue_id), - 'content_type': 'Issue' - } - new_card_request = requests.post(url=new_card_url, headers=projects_headers, - data=json.dumps(new_card_body)) - if new_card_request.status_code == 201: - print('Issue card added to project') - else: - print('Issue card could not be added to project') - class TodoParser(object): """Parser for extracting information from a given diff file.""" @@ -295,8 +208,6 @@ class TodoParser(object): LABELS_PATTERN = re.compile(r'(?<=labels:\s).+', re.IGNORECASE) ASSIGNEES_PATTERN = re.compile(r'(?<=assignees:\s).+', re.IGNORECASE) MILESTONE_PATTERN = re.compile(r'(?<=milestone:\s).+', re.IGNORECASE) - USER_PROJECTS_PATTERN = re.compile(r'(?<=user projects:\s).+', re.IGNORECASE) - ORG_PROJECTS_PATTERN = re.compile(r'(?<=org projects:\s).+', re.IGNORECASE) def __init__(self): # Determine if the Issues should be escaped. @@ -529,8 +440,6 @@ class TodoParser(object): if issue: issues.append(issue) - default_user_projects = os.getenv('INPUT_USER_PROJECTS', None) - default_org_projects = os.getenv('INPUT_ORG_PROJECTS', None) for i, issue in enumerate(issues): # Strip some of the diff symbols so it can be included as a code snippet in the issue body. # Strip removed lines. @@ -541,13 +450,6 @@ class TodoParser(object): cleaned_hunk = re.sub(r'\n\sNo newline at end of file', '', cleaned_hunk, 0, re.MULTILINE) issue.hunk = cleaned_hunk - # If no projects have been specified for this issue, assign any default projects that exist. - if len(issue.user_projects) == 0 and default_user_projects is not None: - separated_user_projects = self._get_projects(f'user projects: {default_user_projects}', 'user') - issue.user_projects = separated_user_projects - if len(issue.org_projects) == 0 and default_org_projects is not None: - separated_org_projects = self._get_projects(f'org projects: {default_org_projects}', 'org') - issue.org_projects = separated_org_projects return issues def _get_language_details(self, language_name, attribute, value): @@ -593,8 +495,6 @@ class TodoParser(object): labels=['todo'], assignees=[], milestone=None, - user_projects=[], - org_projects=[], body=[], hunk=code_block['hunk'], file_name=code_block['file'], @@ -619,18 +519,12 @@ class TodoParser(object): line_labels = self._get_labels(cleaned_line) line_assignees = self._get_assignees(cleaned_line) line_milestone = self._get_milestone(cleaned_line) - user_projects = self._get_projects(cleaned_line, 'user') - org_projects = self._get_projects(cleaned_line, 'org') if line_labels: issue.labels.extend(line_labels) elif line_assignees: issue.assignees.extend(line_assignees) elif line_milestone and not issue.milestone: issue.milestone = line_milestone - elif user_projects: - issue.user_projects.extend(user_projects) - elif org_projects: - issue.org_projects.extend(org_projects) elif len(cleaned_line): if self.should_escape: issue.body.append(self._escape_markdown(cleaned_line)) @@ -763,20 +657,6 @@ class TodoParser(object): milestone = int(milestone) return milestone - def _get_projects(self, comment, projects_type): - """Check the passed comment for projects to link the issue to.""" - projects = [] - if projects_type == 'user': - projects_search = self.USER_PROJECTS_PATTERN.search(comment, re.IGNORECASE) - elif projects_type == 'org': - projects_search = self.ORG_PROJECTS_PATTERN.search(comment, re.IGNORECASE) - else: - return projects - if projects_search: - projects = projects_search.group(0).replace(', ', ',') - projects = list(filter(None, projects.split(','))) - return projects - def _should_ignore(self, file): ignore_patterns = os.getenv('INPUT_IGNORE', None) if ignore_patterns: