{"action":"created","comment":{"url":"https://api.github.com/repos/beproud/beproudbot/pulls/comments/201530886","pull_request_review_id":136051303,"id":201530886,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDIwMTUzMDg4Ng==","diff_hunk":"@@ -0,0 +1,190 @@\n+\"\"\"\n+スクリプト概要\n+~~~~~~~~~~\n+systemdでtimerを作成し、定期的に実行するPython\n+1,Redmineから期限切れ、期限切れそうなチケットを取得する\n+2,チケットの期限によって分類\n+3,各チャンネルに期限切れ、期限が切れそうなチケットの情報を通知する。\n+\"\"\"\n+\n+from redminelib import Redmine\n+from datetime import timedelta, date\n+from slackclient import SlackClient\n+import argparse\n+from textwrap import dedent\n+from configparser import ConfigParser, NoSectionError\n+from collections import defaultdict\n+import time\n+\n+from haro.plugins.redmine_models import ProjectChannel\n+\n+from db import init_dbsession\n+\n+from slackbot_settings import (\n+    REDMINE_URL,\n+    API_KEY,\n+    SLACK_API_TOKEN,\n+    SQLALCHEMY_URL,\n+    SQLALCHEMY_ECHO,\n+    SQLALCHEMY_POOL_SIZE,\n+)\n+\n+from db import Session\n+\n+LIMIT = 7  # 期限が1週間以内のチケットを分別するために使用\n+\n+\n+def get_ticket_information():\n+    \"\"\"Redmineのチケット情報とチケットと結びついているSlackチャンネルを取得\n+    \"\"\"\n+    redmine = Redmine(REDMINE_URL, key=API_KEY)\n+    # すべてのチケットを取得\n+    issues = redmine.issue.filter(status_id='open', sort='due_date')\n+\n+    projects_past_due_date = defaultdict(list)\n+    projects_close_to_due_date = defaultdict(list)\n+    today = date.today()\n+    close_to_today = today + timedelta(LIMIT)\n+    for issue in issues:\n+        # due_date属性とdue_dateがnoneの場合は除外\n+        if not getattr(issue, 'due_date', None):\n+            continue\n+        proj_id = issue.project.id\n+        # issueのデータをSlack通知用にformatする。\n+        proj_rooms = get_proj_room(proj_id)\n+\n+        for proj_room in proj_rooms:\n+            channels = proj_room.channels\n+            if not channels:  # slack channelが設定されていないissueは無視する\n+                continue\n+            elif issue.due_date < today:\n+                # 辞書のkeyと値の例proj_id: ['- 2017-03-31 23872: サーバーセキュリティーの基準を作ろう(@takanory)',]\n+                projects_past_due_date[proj_id].append(issue)\n+            # issueの期限が1週間以内の場合\n+            elif issue.due_date < close_to_today:\n+                projects_close_to_due_date[proj_id].append(issue)\n+\n+    # 各プロジェクトのチケット通知をSlackチャンネルに送る。\n+    send_ticket_info_to_channels(projects_past_due_date, True)\n+    send_ticket_info_to_channels(projects_close_to_due_date, False)\n+\n+\n+def display_issue(issue):\n+    \"\"\"issueの詳細をSlackに表示用にフォーマットする。\n+\n+    フォーマット例:\n+    - 2017-03-31 23872: サーバーセキュリティーの基準を作ろう(@takanory)\n+    :param issue: redmineのissue\n+    \"\"\"\n+\n+    return '- {} {}: {} (@{})'.format(issue.due_date, issue.id,\n+                                      issue.subject, issue.author)\n+\n+\n+def send_ticket_info_to_channels(projects, is_past_due_date):\n+    \"\"\"チケット情報を各Slackチャンネルごとに通知する。\n+\n+        :param projects: 期限が切れたプロジェクト、期限が切れそうなプロジェクトのdict\n+        :param type: int type=0 -> 期限切れ type=1 ->期限切れそうなチケット\n+    \"\"\"\n+    for project in projects.keys():\n+        # 各プロジェクトのproj_roomを獲得する。\n+        # TODO: 毎回DBを検索してredmineに紐付いたSlackのチャンネルを取得せず、最初にまとめて取得する\n+        proj_rooms = get_proj_room(project)\n+\n+        # api_call()を使用し、すべてのSlackチャンネルに期限が切れたチケット、期限が切れそうな通知をチケットまとめて送る\n+        # 1つのredmineプロジェクトが複数のslackチャンネルに関連付けられているケースに対応\n+        for proj_room in proj_rooms:\n+            channels = proj_room.channels\n+            # プロジェクトごとのチケット数カウントを取得\n+            issue_count = len(projects[project])\n+            if is_past_due_date:  # 期限切れチケット\n+                message = '期限が切れたチケットは' + str(issue_count) + ' 件です\\n'\n+            else:  # 期限切れそうなチケット\n+                message = 'もうすぐ期限が切れそうなチケットは' + str(issue_count) + ' 件です\\n'\n+            for issue in projects[project]:\n+                # 通知メッセージをformatする。\n+                message += display_issue(issue) + '\\n'\n+\n+            for channel in channels:\n+                # TODO: JSONでattachmentsを送信する\n+                send_slack_message_per_sec(channel, message)\n+\n+\n+def get_proj_room(project_id):\n+    s = Session()\n+    return s.query(ProjectChannel).filter(\n+        ProjectChannel.project_id == project_id).all()\n+\n+\n+def send_slack_message(channel, message):","path":"src/redmine_notification.py","position":120,"original_position":120,"commit_id":"fa31f1cb7f4976493b8e980c9fac18d28e803dfa","original_commit_id":"fa31f1cb7f4976493b8e980c9fac18d28e803dfa","user":{"login":"daikikuchi","id":25555600,"node_id":"MDQ6VXNlcjI1NTU1NjAw","avatar_url":"https://avatars0.githubusercontent.com/u/25555600?v=4","gravatar_id":"","url":"https://api.github.com/users/daikikuchi","html_url":"https://github.com/daikikuchi","followers_url":"https://api.github.com/users/daikikuchi/followers","following_url":"https://api.github.com/users/daikikuchi/following{/other_user}","gists_url":"https://api.github.com/users/daikikuchi/gists{/gist_id}","starred_url":"https://api.github.com/users/daikikuchi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/daikikuchi/subscriptions","organizations_url":"https://api.github.com/users/daikikuchi/orgs","repos_url":"https://api.github.com/users/daikikuchi/repos","events_url":"https://api.github.com/users/daikikuchi/events{/privacy}","received_events_url":"https://api.github.com/users/daikikuchi/received_events","type":"User","site_admin":false},"body":"こちらもdocstringを書きます。","created_at":"2018-07-10T23:56:48Z","updated_at":"2018-07-10T23:56:49Z","html_url":"https://github.com/beproud/beproudbot/pull/141#discussion_r201530886","pull_request_url":"https://api.github.com/repos/beproud/beproudbot/pulls/141","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/beproud/beproudbot/pulls/comments/201530886"},"html":{"href":"https://github.com/beproud/beproudbot/pull/141#discussion_r201530886"},"pull_request":{"href":"https://api.github.com/repos/beproud/beproudbot/pulls/141"}},"in_reply_to_id":201209245},"pull_request":{"url":"https://api.github.com/repos/beproud/beproudbot/pulls/141","id":197582983,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk3NTgyOTgz","html_url":"https://github.com/beproud/beproudbot/pull/141","diff_url":"https://github.com/beproud/beproudbot/pull/141.diff","patch_url":"https://github.com/beproud/beproudbot/pull/141.patch","issue_url":"https://api.github.com/repos/beproud/beproudbot/issues/141","number":141,"state":"open","locked":false,"title":"[WIP]redmineの期限切れタスクを定期通知","user":{"login":"daikikuchi","id":25555600,"node_id":"MDQ6VXNlcjI1NTU1NjAw","avatar_url":"https://avatars0.githubusercontent.com/u/25555600?v=4","gravatar_id":"","url":"https://api.github.com/users/daikikuchi","html_url":"https://github.com/daikikuchi","followers_url":"https://api.github.com/users/daikikuchi/followers","following_url":"https://api.github.com/users/daikikuchi/following{/other_user}","gists_url":"https://api.github.com/users/daikikuchi/gists{/gist_id}","starred_url":"https://api.github.com/users/daikikuchi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/daikikuchi/subscriptions","organizations_url":"https://api.github.com/users/daikikuchi/orgs","repos_url":"https://api.github.com/users/daikikuchi/repos","events_url":"https://api.github.com/users/daikikuchi/events{/privacy}","received_events_url":"https://api.github.com/users/daikikuchi/received_events","type":"User","site_admin":false},"body":"Redmineの期限切れ、もうすぐ期限が切れそうなチケット情報を各Slackチャンネルに送るPython Scriptを作成。\r\n\r\nチケットURL\r\n\r\n- #137 \r\n\r\nこのレビューで確認してほしい点\r\n\r\n- [ ] 実装の方向性は適切か？\r\n- [ ] redmineからチケットを獲得する方法は正しいか？\r\n- [ ] 期限切れチケットと期限切れそうなチケットを分類できているか？\r\n- [ ] Slackチャンネルに送付できているか？\r\n\r\n","created_at":"2018-06-26T23:59:17Z","updated_at":"2018-07-10T23:56:49Z","closed_at":null,"merged_at":null,"merge_commit_sha":"e03efa054b7eca07b3dcbeddb538edf93a3c3a35","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/beproud/beproudbot/pulls/141/commits","review_comments_url":"https://api.github.com/repos/beproud/beproudbot/pulls/141/comments","review_comment_url":"https://api.github.com/repos/beproud/beproudbot/pulls/comments{/number}","comments_url":"https://api.github.com/repos/beproud/beproudbot/issues/141/comments","statuses_url":"https://api.github.com/repos/beproud/beproudbot/statuses/fa31f1cb7f4976493b8e980c9fac18d28e803dfa","head":{"label":"beproud:t137-redmine_notification","ref":"t137-redmine_notification","sha":"fa31f1cb7f4976493b8e980c9fac18d28e803dfa","user":{"login":"beproud","id":643795,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0Mzc5NQ==","avatar_url":"https://avatars1.githubusercontent.com/u/643795?v=4","gravatar_id":"","url":"https://api.github.com/users/beproud","html_url":"https://github.com/beproud","followers_url":"https://api.github.com/users/beproud/followers","following_url":"https://api.github.com/users/beproud/following{/other_user}","gists_url":"https://api.github.com/users/beproud/gists{/gist_id}","starred_url":"https://api.github.com/users/beproud/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/beproud/subscriptions","organizations_url":"https://api.github.com/users/beproud/orgs","repos_url":"https://api.github.com/users/beproud/repos","events_url":"https://api.github.com/users/beproud/events{/privacy}","received_events_url":"https://api.github.com/users/beproud/received_events","type":"Organization","site_admin":false},"repo":{"id":71103212,"node_id":"MDEwOlJlcG9zaXRvcnk3MTEwMzIxMg==","name":"beproudbot","full_name":"beproud/beproudbot","owner":{"login":"beproud","id":643795,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0Mzc5NQ==","avatar_url":"https://avatars1.githubusercontent.com/u/643795?v=4","gravatar_id":"","url":"https://api.github.com/users/beproud","html_url":"https://github.com/beproud","followers_url":"https://api.github.com/users/beproud/followers","following_url":"https://api.github.com/users/beproud/following{/other_user}","gists_url":"https://api.github.com/users/beproud/gists{/gist_id}","starred_url":"https://api.github.com/users/beproud/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/beproud/subscriptions","organizations_url":"https://api.github.com/users/beproud/orgs","repos_url":"https://api.github.com/users/beproud/repos","events_url":"https://api.github.com/users/beproud/events{/privacy}","received_events_url":"https://api.github.com/users/beproud/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/beproud/beproudbot","description":"beproud bot system","fork":false,"url":"https://api.github.com/repos/beproud/beproudbot","forks_url":"https://api.github.com/repos/beproud/beproudbot/forks","keys_url":"https://api.github.com/repos/beproud/beproudbot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/beproud/beproudbot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/beproud/beproudbot/teams","hooks_url":"https://api.github.com/repos/beproud/beproudbot/hooks","issue_events_url":"https://api.github.com/repos/beproud/beproudbot/issues/events{/number}","events_url":"https://api.github.com/repos/beproud/beproudbot/events","assignees_url":"https://api.github.com/repos/beproud/beproudbot/assignees{/user}","branches_url":"https://api.github.com/repos/beproud/beproudbot/branches{/branch}","tags_url":"https://api.github.com/repos/beproud/beproudbot/tags","blobs_url":"https://api.github.com/repos/beproud/beproudbot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/beproud/beproudbot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/beproud/beproudbot/git/refs{/sha}","trees_url":"https://api.github.com/repos/beproud/beproudbot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/beproud/beproudbot/statuses/{sha}","languages_url":"https://api.github.com/repos/beproud/beproudbot/languages","stargazers_url":"https://api.github.com/repos/beproud/beproudbot/stargazers","contributors_url":"https://api.github.com/repos/beproud/beproudbot/contributors","subscribers_url":"https://api.github.com/repos/beproud/beproudbot/subscribers","subscription_url":"https://api.github.com/repos/beproud/beproudbot/subscription","commits_url":"https://api.github.com/repos/beproud/beproudbot/commits{/sha}","git_commits_url":"https://api.github.com/repos/beproud/beproudbot/git/commits{/sha}","comments_url":"https://api.github.com/repos/beproud/beproudbot/comments{/number}","issue_comment_url":"https://api.github.com/repos/beproud/beproudbot/issues/comments{/number}","contents_url":"https://api.github.com/repos/beproud/beproudbot/contents/{+path}","compare_url":"https://api.github.com/repos/beproud/beproudbot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/beproud/beproudbot/merges","archive_url":"https://api.github.com/repos/beproud/beproudbot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/beproud/beproudbot/downloads","issues_url":"https://api.github.com/repos/beproud/beproudbot/issues{/number}","pulls_url":"https://api.github.com/repos/beproud/beproudbot/pulls{/number}","milestones_url":"https://api.github.com/repos/beproud/beproudbot/milestones{/number}","notifications_url":"https://api.github.com/repos/beproud/beproudbot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/beproud/beproudbot/labels{/name}","releases_url":"https://api.github.com/repos/beproud/beproudbot/releases{/id}","deployments_url":"https://api.github.com/repos/beproud/beproudbot/deployments","created_at":"2016-10-17T05:35:24Z","updated_at":"2018-06-19T00:18:46Z","pushed_at":"2018-07-10T02:43:40Z","git_url":"git://github.com/beproud/beproudbot.git","ssh_url":"git@github.com:beproud/beproudbot.git","clone_url":"https://github.com/beproud/beproudbot.git","svn_url":"https://github.com/beproud/beproudbot","homepage":"http://www.beproud.jp/","size":7150,"stargazers_count":6,"watchers_count":6,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"archived":false,"open_issues_count":28,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":3,"open_issues":28,"watchers":6,"default_branch":"master"}},"base":{"label":"beproud:master","ref":"master","sha":"51b35e4a670fe807df672674ebacc81432ddc39b","user":{"login":"beproud","id":643795,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0Mzc5NQ==","avatar_url":"https://avatars1.githubusercontent.com/u/643795?v=4","gravatar_id":"","url":"https://api.github.com/users/beproud","html_url":"https://github.com/beproud","followers_url":"https://api.github.com/users/beproud/followers","following_url":"https://api.github.com/users/beproud/following{/other_user}","gists_url":"https://api.github.com/users/beproud/gists{/gist_id}","starred_url":"https://api.github.com/users/beproud/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/beproud/subscriptions","organizations_url":"https://api.github.com/users/beproud/orgs","repos_url":"https://api.github.com/users/beproud/repos","events_url":"https://api.github.com/users/beproud/events{/privacy}","received_events_url":"https://api.github.com/users/beproud/received_events","type":"Organization","site_admin":false},"repo":{"id":71103212,"node_id":"MDEwOlJlcG9zaXRvcnk3MTEwMzIxMg==","name":"beproudbot","full_name":"beproud/beproudbot","owner":{"login":"beproud","id":643795,"node_id":"MDEyOk9yZ2FuaXphdGlvbjY0Mzc5NQ==","avatar_url":"https://avatars1.githubusercontent.com/u/643795?v=4","gravatar_id":"","url":"https://api.github.com/users/beproud","html_url":"https://github.com/beproud","followers_url":"https://api.github.com/users/beproud/followers","following_url":"https://api.github.com/users/beproud/following{/other_user}","gists_url":"https://api.github.com/users/beproud/gists{/gist_id}","starred_url":"https://api.github.com/users/beproud/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/beproud/subscriptions","organizations_url":"https://api.github.com/users/beproud/orgs","repos_url":"https://api.github.com/users/beproud/repos","events_url":"https://api.github.com/users/beproud/events{/privacy}","received_events_url":"https://api.github.com/users/beproud/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/beproud/beproudbot","description":"beproud bot system","fork":false,"url":"https://api.github.com/repos/beproud/beproudbot","forks_url":"https://api.github.com/repos/beproud/beproudbot/forks","keys_url":"https://api.github.com/repos/beproud/beproudbot/keys{/key_id}","collaborators_url":"https://api.github.com/repos/beproud/beproudbot/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/beproud/beproudbot/teams","hooks_url":"https://api.github.com/repos/beproud/beproudbot/hooks","issue_events_url":"https://api.github.com/repos/beproud/beproudbot/issues/events{/number}","events_url":"https://api.github.com/repos/beproud/beproudbot/events","assignees_url":"https://api.github.com/repos/beproud/beproudbot/assignees{/user}","branches_url":"https://api.github.com/repos/beproud/beproudbot/branches{/branch}","tags_url":"https://api.github.com/repos/beproud/beproudbot/tags","blobs_url":"https://api.github.com/repos/beproud/beproudbot/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/beproud/beproudbot/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/beproud/beproudbot/git/refs{/sha}","trees_url":"https://api.github.com/repos/beproud/beproudbot/git/trees{/sha}","statuses_url":"https://api.github.com/repos/beproud/beproudbot/statuses/{sha}","languages_url":"https://api.github.com/repos/beproud/beproudbot/languages","stargazers_url":"https://api.github.com/repos/beproud/beproudbot/stargazers","contributors_url":"https://api.github.com/repos/beproud/beproudbot/contributors","subscribers_url":"https://api.github.com/repos/beproud/beproudbot/subscribers","subscription_url":"https://api.github.com/repos/beproud/beproudbot/subscription","commits_url":"https://api.github.com/repos/beproud/beproudbot/commits{/sha}","git_commits_url":"https://api.github.com/repos/beproud/beproudbot/git/commits{/sha}","comments_url":"https://api.github.com/repos/beproud/beproudbot/comments{/number}","issue_comment_url":"https://api.github.com/repos/beproud/beproudbot/issues/comments{/number}","contents_url":"https://api.github.com/repos/beproud/beproudbot/contents/{+path}","compare_url":"https://api.github.com/repos/beproud/beproudbot/compare/{base}...{head}","merges_url":"https://api.github.com/repos/beproud/beproudbot/merges","archive_url":"https://api.github.com/repos/beproud/beproudbot/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/beproud/beproudbot/downloads","issues_url":"https://api.github.com/repos/beproud/beproudbot/issues{/number}","pulls_url":"https://api.github.com/repos/beproud/beproudbot/pulls{/number}","milestones_url":"https://api.github.com/repos/beproud/beproudbot/milestones{/number}","notifications_url":"https://api.github.com/repos/beproud/beproudbot/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/beproud/beproudbot/labels{/name}","releases_url":"https://api.github.com/repos/beproud/beproudbot/releases{/id}","deployments_url":"https://api.github.com/repos/beproud/beproudbot/deployments","created_at":"2016-10-17T05:35:24Z","updated_at":"2018-06-19T00:18:46Z","pushed_at":"2018-07-10T02:43:40Z","git_url":"git://github.com/beproud/beproudbot.git","ssh_url":"git@github.com:beproud/beproudbot.git","clone_url":"https://github.com/beproud/beproudbot.git","svn_url":"https://github.com/beproud/beproudbot","homepage":"http://www.beproud.jp/","size":7150,"stargazers_count":6,"watchers_count":6,"language":"Python","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":3,"mirror_url":null,"archived":false,"open_issues_count":28,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit","node_id":"MDc6TGljZW5zZTEz"},"forks":3,"open_issues":28,"watchers":6,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/beproud/beproudbot/pulls/141"},"html":{"href":"https://github.com/beproud/beproudbot/pull/141"},"issue":{"href":"https://api.github.com/repos/beproud/beproudbot/issues/141"},"comments":{"href":"https://api.github.com/repos/beproud/beproudbot/issues/141/comments"},"review_comments":{"href":"https://api.github.com/repos/beproud/beproudbot/pulls/141/comments"},"review_comment":{"href":"https://api.github.com/repos/beproud/beproudbot/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/beproud/beproudbot/pulls/141/commits"},"statuses":{"href":"https://api.github.com/repos/beproud/beproudbot/statuses/fa31f1cb7f4976493b8e980c9fac18d28e803dfa"}},"author_association":"NONE"}}
{"actor":{"display_login":"daikikuchi"}}