Add task to add PRs to review to todoist

This commit is contained in:
Jake Howard 2019-02-20 19:33:34 +00:00
parent 7cd7c4685c
commit d2d0f6e291
Signed by: jake
GPG key ID: 57AFB45680EDD477
7 changed files with 127 additions and 63 deletions

View file

@ -9,3 +9,4 @@ Do things
- Healthcheck endpoint - Healthcheck endpoint
- Add assigned issues from certain GitHub repos to Todoist - Add assigned issues from certain GitHub repos to Todoist
- Add all unreviewed PRs for certain GitHub repos to Todoist

View file

@ -3,11 +3,13 @@ import asyncio
from apscheduler.schedulers.asyncio import AsyncIOScheduler from apscheduler.schedulers.asyncio import AsyncIOScheduler
from .todoist_assigned_issues import todoist_assigned_issues from .todoist_assigned_issues import todoist_assigned_issues
from .todoist_repo_prs import todoist_repo_prs
def create_scheduler(): def create_scheduler():
scheduler = AsyncIOScheduler() scheduler = AsyncIOScheduler()
scheduler.add_job(todoist_assigned_issues, 'interval', minutes=15) scheduler.add_job(todoist_assigned_issues, 'interval', minutes=15)
scheduler.add_job(todoist_repo_prs, 'interval', minutes=15)
return scheduler return scheduler

View file

@ -1,10 +1,10 @@
import logging import logging
from typing import Dict
from github import Issue from github import Issue
from actioner.clients import github, todoist from actioner.clients import github, todoist
from actioner.utils import get_todoist_project_from_repo from actioner.utils import get_todoist_project_from_repo
from actioner.utils.github import get_existing_task, get_issue_link
REPOS = frozenset([ REPOS = frozenset([
'srobo/tasks', 'srobo/tasks',
@ -28,25 +28,10 @@ def get_status_for_issue(issue: Issue) -> int:
return max(priorities, default=1) 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: def issue_to_task_name(issue: Issue) -> str:
return get_issue_link(issue) + ": " + issue.title 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(): def todoist_assigned_issues():
me = github.get_user() me = github.get_user()
todoist.projects.sync() todoist.projects.sync()

View 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
View 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

View file

@ -1,55 +1,9 @@
from collections import namedtuple from actioner.scheduler.todoist_assigned_issues import REPOS
from actioner.scheduler.todoist_assigned_issues import (
REPOS,
get_existing_task,
get_issue_link,
issue_to_task_name,
)
from actioner.utils import get_todoist_project_from_repo from actioner.utils import get_todoist_project_from_repo
from tests import BaseTestCase 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): class ConfigurationTestCase(BaseTestCase):
def test_repo_is_known(self): def test_repo_is_known(self):
for repo in REPOS: for repo in REPOS:
self.assertIsNotNone(get_todoist_project_from_repo(repo)) 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'))
)

View 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'))
)