Skip to content

Commit 7f69691

Browse files
Merge pull request #67 from SuffolkLITLab/user-feedback-label
Better logging of token permission issues
2 parents f279cfb + 4a79626 commit 7f69691

File tree

5 files changed

+92
-26
lines changed

5 files changed

+92
-26
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ This package is designed to support the following workflow:
101101
issue. This interview also will show you the list of emails of users who agreed to join a
102102
qualitative research panel.
103103

104+
6. If the issue label "user feedback" is present on the repo, it will be used by default to label incoming issues.
105+
104106
## Author
105107

106108
Quinten Steenhuis, qsteenhuis@suffolk.edu
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.5.2'
1+
__version__ = '0.5.3'

docassemble/GithubFeedbackForm/data/questions/feedback.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ code: |
318318
elif is_likely_spam(issue_template.content):
319319
log("Not saving feedback because it looks like spam")
320320
mark_task_as_performed('issue noted', persistent=True)
321-
issue_url, saved_uuid = None
321+
issue_url = saved_uuid = None
322322
note_issue = False # End block early
323323
else:
324324
saved_uuid # Trigger the code to save locally on the server and optionally link the session answers. We do this regardless of whether we send to GitHub

docassemble/GithubFeedbackForm/github_issue.py

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -363,23 +363,32 @@ def make_github_issue(
363363
label: Optional[str] = None,
364364
) -> Optional[str]:
365365
"""
366-
Create a new Github issue and return the URL.
366+
Create a new GitHub issue and return the URL.
367367
368-
template - the docassemble template for the github issue. Overrides `title` and `body` if provided.
369-
title - the title for the github issue
370-
body - the body of the github issue
368+
Args:
369+
template: a docassemble template that overrides `title` and `body`
370+
title: the title for the GitHub issue
371+
body: the body of the GitHub issue
372+
label: optional label to add *if* we can verify or create it
373+
374+
At least one of template, title, and body is required.
375+
376+
Returns:
377+
str, the URL for the label if it exists, or None if the issue could not be created
371378
"""
372379
make_issue_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/issues"
373-
# Headers
380+
381+
# Abort early if the configuration or repo owner is invalid
374382
if not valid_github_issue_config():
375383
log(
376-
"Error creating issue: No valid GitHub token provided. Check your config and see https://github.com/SuffolkLITLab/docassemble-GithubFeedbackForm#getting-started"
384+
"Error creating issue: No valid GitHub token provided. "
385+
"See https://github.com/SuffolkLITLab/docassemble-GithubFeedbackForm#getting-started"
377386
)
378387
return None
379-
380388
if repo_owner.lower() not in _get_allowed_repo_owners():
381389
log(
382-
f"Error creating issue: this form is not permitted to add issues to repositories owned by {repo_owner}. Check your config and see https://github.com/SuffolkLITLab/docassemble-GithubFeedbackForm#getting-started"
390+
f"Error creating issue: repositories owned by {repo_owner} are not permitted. "
391+
"See https://github.com/SuffolkLITLab/docassemble-GithubFeedbackForm#getting-started"
383392
)
384393
return None
385394

@@ -388,46 +397,101 @@ def make_github_issue(
388397
"Accept": "application/vnd.github.v3+json",
389398
}
390399

400+
# Abort early for private repos
401+
repo_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}"
402+
repo_resp = requests.get(repo_url, headers=headers)
403+
404+
if repo_resp.status_code != 200:
405+
log(
406+
f"Cannot access repo {repo_owner}/{repo_name}: "
407+
f"{repo_resp.status_code} {repo_resp.text}. Maybe it is a private repo?"
408+
"Check that the PAT has the correct scopes and that the user can write to the repo."
409+
)
410+
return None
411+
412+
# ------------------------------------------------------------------
413+
# 1. Figure out whether we can safely apply the label
414+
# ------------------------------------------------------------------
415+
apply_label = False # only set to True when we're sure it exists
416+
391417
if label:
392-
labels_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/labels"
393-
has_label_resp = requests.get(labels_url + "/" + label, headers=headers)
394-
if has_label_resp.status_code == 404:
418+
make_labels_url = (
419+
f"https://api.github.com/repos/{repo_owner}/{repo_name}/labels"
420+
)
421+
get_labels_url = f"{make_labels_url}/{label}"
422+
has_label_resp = requests.get(get_labels_url, headers=headers)
423+
424+
if has_label_resp.status_code == 200:
425+
# Label already exists in the repo
426+
apply_label = True
427+
428+
elif has_label_resp.status_code == 404:
429+
# Try to create the label; this may fail if the token lacks permission
395430
label_data = {
396431
"name": label,
397432
"description": "Feedback from a Docassemble Interview",
398433
"color": "002E60",
399434
}
400435
make_label_resp = requests.post(
401-
labels_url, data=json.dumps(label_data), headers=headers
436+
make_labels_url,
437+
data=json.dumps(label_data),
438+
headers=headers,
402439
)
403440
if make_label_resp.status_code == 201:
404-
log("Created the {label} label for the {make_issue_url} repo")
441+
log(
442+
f"Created the '{label}' label for the "
443+
f"{repo_owner}/{repo_name} repository"
444+
)
445+
apply_label = True
405446
else:
406447
log(
407-
f"Was not able to find nor create the {label} label: {make_label_resp.text}"
448+
f"Could not create label '{label}': {make_label_resp.status_code} "
449+
f"{make_label_resp.text}"
408450
)
409-
label = None
451+
else:
452+
# 403, 422, etc. → most likely a permissions issue; skip using the label
453+
log(
454+
f"Unable to verify label '{label}': {has_label_resp.status_code} "
455+
f"{has_label_resp.text}"
456+
)
410457

458+
# ------------------------------------------------------------------
459+
# 2. Derive title/body from a template, if supplied
460+
# ------------------------------------------------------------------
411461
if template:
412-
title = template.subject
413-
body = template.content
462+
if hasattr(template, "subject"):
463+
title = template.subject
464+
if hasattr(template, "body"):
465+
body = template.content
466+
467+
if not title and not body:
468+
return None
469+
470+
if not body:
471+
body = ""
472+
473+
if not title:
474+
title = "User feedback"
414475

476+
# Reject obvious spam before calling GitHub
415477
if is_likely_spam(body):
416-
log("Error creating issue: the body of the issue is caught as spam")
478+
log("Error creating issue: the body of the issue was classified as spam")
417479
return None
418480

419-
# Create our issue
420-
data: Dict[str, Union[None, str, List[str]]] = {
481+
# ------------------------------------------------------------------
482+
# 3. Assemble and POST the issue
483+
# ------------------------------------------------------------------
484+
data: Dict[str, Union[str, List[str]]] = {
421485
"title": title,
422486
"body": body,
423487
}
424-
if label:
488+
if apply_label and label is not None:
425489
data["labels"] = [label]
426490

427-
# Add the issue to our repository
428491
response = requests.post(make_issue_url, data=json.dumps(data), headers=headers)
492+
429493
if response.status_code == 201:
430494
return response.json().get("html_url")
431495
else:
432-
log(f'Could not create Issue "{title}", results {response.text}')
496+
log(f'Could not create issue "{title}": {response.status_code} {response.text}')
433497
return None

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def find_package_data(where='.', package='', exclude=standard_exclude, exclude_d
4343
return out
4444

4545
setup(name='docassemble.GithubFeedbackForm',
46-
version='0.5.2',
46+
version='0.5.3',
4747
description=('Integration of GitHub issues with Docassemble interviews for collecting user feedback'),
4848
long_description='# docassemble.GithubFeedbackForm\r\n\r\nA package that uses the GitHub API to gather feedback and then submit issues to Github that can be embedded\r\ninto a Docassemble interview. Makes it easy to collect per-page feedback.\r\n\r\nThis package is designed to support the following workflow:\r\n\r\n1. Work is stored on a public GitHub repository, or at least, you setup a repository to collect feedback.\r\n1. There is one package per "interview"/"app".\r\n2. Each question block has a unique question ID.\r\n3. Preferably--questions are triggered in an interview order block. If you use a series of `mandatory`\r\n blocks instead of a single mandatory block, the `variable` listed in the bug report may not be as useful.\r\n\r\n## Getting started\r\n\r\n1. Create a new GitHub user and create a personal access token on it. The personal access\r\n token needs minimal permissions. Specifically, it needs to be allowed to make pull requests.\r\n Pull request access is allowed for anyone by default when you create a new, public GitHub repository.\r\n3. Edit your config, and create a block like this:\r\n\r\n```yaml\r\ngithub issues:\r\n username: "YOUR_NEW_DEDICATED_ISSUE_CREATION_ACCOUNT"\r\n token: "..." # A valid GitHub personal access token associated with the username above\r\n default repository owner: YOUR_GITHUB_USER_OR_ORG_HERE\r\n allowed repository owners: # List the repo that your account will be allowed to create issues on\r\n - YOUR_GITHUB_USER_OR_ORG_HERE \r\n - SECOND_GITHUB_USER_OR_ORG\r\n```\r\n\r\n Note that it is important to provide a list of allowed repository owners.\r\n This is used to prevent your form from being used to spam GitHub \r\n repositories with feedback.\r\n \r\n3. Add a link on each page, in the footer or `under` area. \r\n You can use the `feedback_link()` function to add a link, like this:\r\n `[:comment-dots: Feedback](${ feedback_link(user_info()) } ){:target="_blank"}`\r\n \r\n Optional parameters:\r\n - `i`: the feedback form, like: docassemble.AssemblyLine:feedback.yml\r\n - `github_repo`: repo name, like: docassemble-AssemblyLine\r\n - `github_user`: owner of the repo, like: suffolklitlab\r\n - `variable`: variable being sought, like: intro\r\n - `question_id`: id of the current question, like: intro\r\n - `package_version`: version number of the current package\r\n - `filename`: filename of the interview the user is providing feedback on.\r\n \r\n Each has a sensible default. Most likely, you will limit your custom\r\n parameters to the `github_repo` if you want feedback links to work\r\n from the docassemble playground.\r\n \r\n You will also need to include the `github_issue.py` module in your parent interview,\r\n like this: \r\n ```yaml\r\n ---\r\n modules:\r\n - docassemble.GithubFeedbackForm.github_issue\r\n ```\r\n \r\n4. Optionally, create your own feedback.yml file. If you want a custom feedback.yml,\r\n it should look like this, with whatever customizations you choose:\r\n\r\n```yaml\r\ninclude:\r\n - docassemble.GithubFeedbackForm:feedback.yml\r\n---\r\ncode: |\r\n al_feedback_form_title = "Your title here" \r\n---\r\ncode: |\r\n # This email will be used ONLY if there is no valid GitHub config\r\n al_error_email = "your_email@yourdomain.com"\r\n---\r\ntemplate: al_how_to_get_legal_help\r\ncontent: |\r\n If you need more help, these are free resources:\r\n\r\n ... [INCLUDE STATE-SPECIFIC RESOURCES] \r\n```\r\n\r\nYou may also want to customize the metadata: title, exit url and override \r\nany specific questions, add a logo, etc. \r\n\r\n## Author\r\n\r\nQuinten Steenhuis, qsteenhuis@suffolk.edu\r\n',
4949
long_description_content_type='text/markdown',

0 commit comments

Comments
 (0)