Add task to add PRs to review to todoist
This commit is contained in:
parent
7cd7c4685c
commit
d2d0f6e291
7 changed files with 127 additions and 63 deletions
|
@ -9,3 +9,4 @@ Do things
|
|||
|
||||
- Healthcheck endpoint
|
||||
- Add assigned issues from certain GitHub repos to Todoist
|
||||
- Add all unreviewed PRs for certain GitHub repos to Todoist
|
||||
|
|
|
@ -3,11 +3,13 @@ import asyncio
|
|||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
from .todoist_assigned_issues import todoist_assigned_issues
|
||||
from .todoist_repo_prs import todoist_repo_prs
|
||||
|
||||
|
||||
def create_scheduler():
|
||||
scheduler = AsyncIOScheduler()
|
||||
scheduler.add_job(todoist_assigned_issues, 'interval', minutes=15)
|
||||
scheduler.add_job(todoist_repo_prs, 'interval', minutes=15)
|
||||
return scheduler
|
||||
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from github import Issue
|
||||
|
||||
from actioner.clients import github, todoist
|
||||
from actioner.utils import get_todoist_project_from_repo
|
||||
from actioner.utils.github import get_existing_task, get_issue_link
|
||||
|
||||
REPOS = frozenset([
|
||||
'srobo/tasks',
|
||||
|
@ -28,25 +28,10 @@ def get_status_for_issue(issue: Issue) -> int:
|
|||
return max(priorities, default=1)
|
||||
|
||||
|
||||
def get_issue_link(issue: Issue) -> str:
|
||||
return "[#{id}]({url})".format(
|
||||
id=issue.number,
|
||||
url=issue.html_url
|
||||
)
|
||||
|
||||
|
||||
def issue_to_task_name(issue: Issue) -> str:
|
||||
return get_issue_link(issue) + ": " + issue.title
|
||||
|
||||
|
||||
def get_existing_task(tasks: Dict[int, str], issue: Issue):
|
||||
issue_link = get_issue_link(issue)
|
||||
for task_id, task_title in tasks.items():
|
||||
if task_title.startswith(issue_link):
|
||||
return task_id
|
||||
return None
|
||||
|
||||
|
||||
def todoist_assigned_issues():
|
||||
me = github.get_user()
|
||||
todoist.projects.sync()
|
||||
|
|
70
actioner/scheduler/todoist_repo_prs.py
Normal file
70
actioner/scheduler/todoist_repo_prs.py
Normal file
|
@ -0,0 +1,70 @@
|
|||
import logging
|
||||
|
||||
from github import PullRequest
|
||||
|
||||
from actioner.clients import github, todoist
|
||||
from actioner.utils import get_todoist_project_from_repo
|
||||
from actioner.utils.github import get_existing_task, get_issue_link
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
REPOS = frozenset([
|
||||
'srobo/core-team-minutes'
|
||||
])
|
||||
|
||||
|
||||
def pr_to_task_name(pr: PullRequest) -> str:
|
||||
return "Review " + get_issue_link(pr) + ": " + pr.title
|
||||
|
||||
|
||||
def get_my_review(me, pr: PullRequest):
|
||||
for review in pr.get_reviews():
|
||||
if review.user.login == me.login:
|
||||
return review
|
||||
|
||||
|
||||
def todoist_repo_prs():
|
||||
me = github.get_user()
|
||||
todoist.projects.sync()
|
||||
todoist.items.sync()
|
||||
for repo_name in REPOS:
|
||||
project_id = get_todoist_project_from_repo(repo_name)
|
||||
existing_tasks = {item['id']: item['content'] for item in todoist.state['items'] if item['project_id'] == project_id}
|
||||
repo = github.get_repo(repo_name)
|
||||
for pr in repo.get_pulls(state='all'):
|
||||
existing_task_id = get_existing_task(existing_tasks, pr)
|
||||
|
||||
if pr.state == 'closed' and existing_task_id:
|
||||
my_review = get_my_review(me, pr)
|
||||
if pr.merged and my_review and my_review.state == 'APPROVED':
|
||||
logger.info("Completing task to review '{}'".format(pr.title))
|
||||
todoist.items.complete([existing_task_id])
|
||||
else:
|
||||
logger.info("Deleting task to review '{}'".format(pr.title))
|
||||
todoist.items.delete([existing_task_id])
|
||||
|
||||
elif pr.state == 'open':
|
||||
if existing_task_id is None:
|
||||
logger.info("Creating task to review '{}'".format(pr.title))
|
||||
existing_task_id = todoist.items.add(
|
||||
pr_to_task_name(pr),
|
||||
project_id
|
||||
)['id']
|
||||
|
||||
existing_task = todoist.items.get_by_id(existing_task_id)
|
||||
my_review = get_my_review(me, pr)
|
||||
if existing_task_id and my_review:
|
||||
if my_review.commit_id == pr.head.sha and not existing_task['checked']:
|
||||
logger.info("Completing task to review '{}'".format(pr.title))
|
||||
todoist.items.complete([existing_task_id])
|
||||
elif existing_task['checked']:
|
||||
logger.info("Re-opening task to review '{}'".format(pr.title))
|
||||
todoist.items.uncomplete([existing_task_id])
|
||||
existing_task.update(
|
||||
content=pr_to_task_name(pr)
|
||||
)
|
||||
if pr.milestone and pr.milestone.due_on:
|
||||
existing_task.update(date_string=pr.milestone.due_on.strftime("%d/%m/%Y"))
|
||||
|
||||
todoist.commit()
|
16
actioner/utils/github.py
Normal file
16
actioner/utils/github.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from typing import Dict
|
||||
|
||||
|
||||
def get_issue_link(issue_or_pr) -> str:
|
||||
return "[#{id}]({url})".format(
|
||||
id=issue_or_pr.number,
|
||||
url=issue_or_pr.html_url
|
||||
)
|
||||
|
||||
|
||||
def get_existing_task(tasks: Dict[int, str], issue_or_pr):
|
||||
issue_link = get_issue_link(issue_or_pr)
|
||||
for task_id, task_title in tasks.items():
|
||||
if issue_link in task_title:
|
||||
return task_id
|
||||
return None
|
|
@ -1,55 +1,9 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from actioner.scheduler.todoist_assigned_issues import (
|
||||
REPOS,
|
||||
get_existing_task,
|
||||
get_issue_link,
|
||||
issue_to_task_name,
|
||||
)
|
||||
from actioner.scheduler.todoist_assigned_issues import REPOS
|
||||
from actioner.utils import get_todoist_project_from_repo
|
||||
from tests import BaseTestCase
|
||||
|
||||
FakeIssue = namedtuple('FakeIssue', ['number', 'html_url', 'title'])
|
||||
|
||||
|
||||
class IssueTaskNameTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.issue = FakeIssue(123, 'https://github.com/repo/thing', 'issue title')
|
||||
|
||||
def test_creates_link(self):
|
||||
self.assertEqual(get_issue_link(self.issue), "[#123](https://github.com/repo/thing)")
|
||||
self.assertIn(self.issue.html_url, get_issue_link(self.issue))
|
||||
|
||||
def test_task_name_contains_title(self):
|
||||
self.assertIn(self.issue.title, issue_to_task_name(self.issue))
|
||||
|
||||
def test_task_name_contains_link(self):
|
||||
self.assertIn(get_issue_link(self.issue), issue_to_task_name(self.issue))
|
||||
|
||||
|
||||
class ConfigurationTestCase(BaseTestCase):
|
||||
def test_repo_is_known(self):
|
||||
for repo in REPOS:
|
||||
self.assertIsNotNone(get_todoist_project_from_repo(repo))
|
||||
|
||||
|
||||
class ExistingTaskTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.tasks = {
|
||||
123: '[#1](url): title',
|
||||
456: '[#2](url/2): title 2',
|
||||
789: '[#3](url/3): title 3',
|
||||
}
|
||||
|
||||
def test_finds_existing_repos(self):
|
||||
self.assertEqual(
|
||||
get_existing_task(self.tasks, FakeIssue(1, 'url', 'title')),
|
||||
123
|
||||
)
|
||||
|
||||
def test_not_existing_repo(self):
|
||||
self.assertIsNone(
|
||||
get_existing_task(self.tasks, FakeIssue(123, 'url', 'title'))
|
||||
)
|
||||
|
|
36
tests/test_utils/test_github.py
Normal file
36
tests/test_utils/test_github.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from collections import namedtuple
|
||||
|
||||
from actioner.utils.github import get_existing_task, get_issue_link
|
||||
from tests import BaseTestCase
|
||||
|
||||
FakeIssue = namedtuple('FakeIssue', ['number', 'html_url', 'title'])
|
||||
|
||||
|
||||
class IssueLinkTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.issue = FakeIssue(123, 'https://github.com/repo/thing', 'issue title')
|
||||
|
||||
def test_creates_link(self):
|
||||
self.assertEqual(get_issue_link(self.issue), "[#123](https://github.com/repo/thing)")
|
||||
|
||||
|
||||
class ExistingTaskTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.tasks = {
|
||||
123: '[#1](url): title',
|
||||
456: '[#2](url/2): title 2',
|
||||
789: '[#3](url/3): title 3',
|
||||
}
|
||||
|
||||
def test_finds_existing_repos(self):
|
||||
self.assertEqual(
|
||||
get_existing_task(self.tasks, FakeIssue(1, 'url', 'title')),
|
||||
123
|
||||
)
|
||||
|
||||
def test_not_existing_repo(self):
|
||||
self.assertIsNone(
|
||||
get_existing_task(self.tasks, FakeIssue(123, 'url', 'title'))
|
||||
)
|
Reference in a new issue