From 59e896a7edfaaa290a09fd252fb2d204c0db0aeb Mon Sep 17 00:00:00 2001 From: Hepheir Date: Thu, 1 Aug 2024 22:33:17 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20`MemberManager`=EC=97=90=20Sing?= =?UTF-8?q?leton=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handler/controller.py | 13 ++----------- src/implementation/member_finder.py | 8 ++++++++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/handler/controller.py b/src/handler/controller.py index 5e73dc7..058bd77 100644 --- a/src/handler/controller.py +++ b/src/handler/controller.py @@ -9,15 +9,6 @@ from implementation.member_finder import MemberManager from implementation.slack_client import SlackClient -MEMBER_MANAGER = None - - -def _get_member_manager(): # TODO(seonghyeok): we need better singleton - global MEMBER_MANAGER - if not MEMBER_MANAGER: - MEMBER_MANAGER = MemberManager(GoogleSpreadsheetClient()) - return MEMBER_MANAGER - # reaction_added event sample: # { @@ -40,7 +31,7 @@ def join_bigchat(event, say, client): envs.JOIN_BIGCHAT_EMOJI, SlackClient(say, client), GoogleSpreadsheetClient(), - _get_member_manager(), + MemberManager.get_instance(), ).run() @@ -52,7 +43,7 @@ def abandon_bigchat(event, say, client): envs.ANNA_ID, envs.JOIN_BIGCHAT_EMOJI, SlackClient(say, client), - _get_member_manager(), + MemberManager.get_instance(), GoogleSpreadsheetClient(), ).run() diff --git a/src/implementation/member_finder.py b/src/implementation/member_finder.py index d55ae18..319b3ea 100644 --- a/src/implementation/member_finder.py +++ b/src/implementation/member_finder.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from typing import Dict, List @@ -35,6 +37,12 @@ class MemberLackInfo(Exception): class MemberManager: + @classmethod + def get_instance(cls) -> MemberManager: + if not hasattr(cls, "_instance"): + cls._instance = cls(GoogleSpreadsheetClient()) + return cls._instance + def __init__(self, gs_client: GoogleSpreadsheetClient): self.gs_client = gs_client self.members_worksheet_id = int(envs.MEMBERS_INFO_WORKSHEET_ID) From d9f50b33da958dc34130146be9a5767a7cfa74c3 Mon Sep 17 00:00:00 2001 From: Hepheir Date: Thu, 1 Aug 2024 19:11:46 +0900 Subject: [PATCH 2/5] refactor: `strip_multipline` -> `textwrap.dedent` --- src/handler/bigchat/join_bigchat.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/handler/bigchat/join_bigchat.py b/src/handler/bigchat/join_bigchat.py index 732cfef..2d3989b 100644 --- a/src/handler/bigchat/join_bigchat.py +++ b/src/handler/bigchat/join_bigchat.py @@ -1,9 +1,10 @@ from typing import List import re +import textwrap from implementation.member_finder import MemberNotFound, MemberLackInfo from implementation.slack_client import Message -from util.utils import strip_multiline + SPREADSHEET_PAT = re.compile( r"https://docs.google.com/spreadsheets/d/.*/edit#gid=(\d*)" @@ -60,7 +61,7 @@ def run(self): self.slack_client.send_message(msg=f"<@{self.user}>, 등록 완료!", ts=self.ts) self.slack_client.send_message_only_visible_to_user( - msg=strip_multiline( + msg=textwrap.dedent( f""" <@{self.user}> 네 신청 정보를 아래와 같이 등록했어. 바뀐 부분이 있다면 운영진에게 DM으로 알려줘! ``` @@ -69,7 +70,8 @@ def run(self): 이메일: {member.email} 학교/회사: {member.school_name_or_company_name} ``` - (참고로 이 메시지는 너만 볼 수 있어!)""" + (참고로 이 메시지는 너만 볼 수 있어!) + """ ), channel=self.channel, ts=self.ts, From 84f9d50692fc04d74e0ad3e5885065fb60516824 Mon Sep 17 00:00:00 2001 From: Hepheir Date: Fri, 2 Aug 2024 02:11:44 +0900 Subject: [PATCH 3/5] =?UTF-8?q?feat:=20slack=20client=EC=97=90=20=ED=8A=B9?= =?UTF-8?q?=EC=A0=95=20=EB=A9=94=EC=8B=9C=EC=A7=80=EC=97=90=20=EB=8B=AC?= =?UTF-8?q?=EB=A6=B0=20=ED=8A=B9=EC=A0=95=20=EC=9D=B4=EB=AA=A8=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EB=88=84=EB=A5=BC=20=EC=82=AC=EB=9E=8C=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=EC=9D=84=20=EB=B0=98=ED=99=98=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/implementation/slack_client.py | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/implementation/slack_client.py b/src/implementation/slack_client.py index bece05e..26b2653 100644 --- a/src/implementation/slack_client.py +++ b/src/implementation/slack_client.py @@ -19,6 +19,12 @@ class Emoji(BaseModel): name: str +class Reaction(BaseModel): + name: str + users: List[str] + count: int + + class SlackClient: def __init__(self, say: Say, web_client: WebClient): self.say = say @@ -50,6 +56,10 @@ def _messages_to_members(messages, channel): for msg in messages ] + def get_channel(self) -> Optional[str]: + """현재 메시지가 발송된 채널을 반환한다.""" + return self.say.channel + def get_replies( self, channel: str, thread_ts: str = None, ts: str = None ) -> List[Message]: @@ -88,6 +98,27 @@ def add_emoji(self, channel, ts, emoji_name): return raise ex + def get_emoji(self, channel: str, ts: str, emoji_name: str) -> Optional[Reaction]: + """channel에 있는 ts 시간에 발송된 메시지에 사용자들이 남긴 반응 목록을 가져온다. + + 해당 반응이 존재하지 않는다면 None을 반환한다.""" + response = self.web_client.reactions_get( + channel=channel, + full=True, + timestamp=ts, + ) + assert response["ok"] + assert response["type"] == "message" + for reaction in response["message"]["reactions"]: + if reaction["name"] == emoji_name: + return Reaction( + name=reaction["name"], + users=reaction["users"], + count=reaction["count"], + ) + else: + return None + def remove_emoji(self, channel, ts, emoji_name): try: self.web_client.reactions_remove( From f226f265940f7bc54ae9673d0e7c1ec3ac386f87 Mon Sep 17 00:00:00 2001 From: Hepheir Date: Fri, 2 Aug 2024 02:15:57 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat:=20=EB=B9=85=EC=B1=97=20=EC=8A=A4?= =?UTF-8?q?=ED=94=84=EB=A0=88=EB=93=9C=20=EC=8B=9C=ED=8A=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=9B=84=20GOGO=20=EC=9D=B4=EB=AA=A8=EC=A7=80?= =?UTF-8?q?=EB=A5=BC=20=EB=88=84=EB=A5=B8=20=EC=82=AC=EB=9E=8C=EB=93=A4?= =?UTF-8?q?=EC=9D=84=20=EC=9D=BC=EA=B4=84=20=EB=93=B1=EB=A1=9D=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84=20?= =?UTF-8?q?(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/handler/bigchat/create_bigchat_sheet.py | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/handler/bigchat/create_bigchat_sheet.py b/src/handler/bigchat/create_bigchat_sheet.py index a117b2b..8cbff85 100644 --- a/src/handler/bigchat/create_bigchat_sheet.py +++ b/src/handler/bigchat/create_bigchat_sheet.py @@ -1,3 +1,7 @@ +from implementation.slack_client import Reaction +from implementation.member_finder import MemberManager, MemberNotFound, MemberLackInfo + + class CreateBigchatSheet: def __init__(self, event, slack_client, gs_client): self.text = event["text"] @@ -9,6 +13,7 @@ def run(self): if "새로운 빅챗" not in self.text: return False + # TODO: REGEX로 더 깔끔하게 따올 수 있지 않을까? sheet_name = self.text.split("새로운 빅챗", maxsplit=1)[1].split("\n")[0].strip() if not sheet_name: self.slack_client.send_message(msg="시트 이름이 입력되지 않았어. 다시 입력해줘!", ts=self.ts) @@ -16,8 +21,33 @@ def run(self): worksheet_id = self.gs_client.create_bigchat_sheet(sheet_name) sheet_url = self.gs_client.get_url(worksheet_id) + self.slack_client.send_message( msg=f"새로운 빅챗, 등록 완료! <{sheet_url}|{sheet_name}> :google_spreadsheets:", ts=self.ts, ) + + # 빅챗 시트가 생성되기 이전에 등록을 시도한(GOGO 이모지를 누른) + # 인원들이 누락된 것에 대한 사후처리 + channel = self.slack_client.get_channel() + assert channel is not None + reaction = self.slack_client.get_emoji( + channel=channel, + timestamp=self.ts, + ) + if reaction is not None: + reaction: Reaction + for user in reaction.users: + error_message = None + try: + member = MemberManager.get_instance().find(user) + except MemberNotFound: + error_message = f"<@{user}>, 네 정보를 찾지 못했어. 운영진에게 연락해줘!" + except MemberLackInfo: + error_message = f"<@{user}>, 네 정보에 누락된 값이 있어. 운영진에게 연락해줘!" + else: + self.gs_client.append_row(worksheet_id, member.transform_for_spreadsheet()) + finally: + if error_message: + self.slack_client.send_message(msg=error_message, ts=self.ts) return True From 09eff34b529efc28a84f2cce1d51636853b52cf3 Mon Sep 17 00:00:00 2001 From: Hepheir Date: Fri, 2 Aug 2024 02:16:42 +0900 Subject: [PATCH 5/5] =?UTF-8?q?test:=20=EB=B9=85=EC=B1=97=20=EC=8A=A4?= =?UTF-8?q?=ED=94=84=EB=A0=88=EB=93=9C=20=EC=8B=9C=ED=8A=B8=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9D=B4=20=EC=99=84=EB=A3=8C=EB=90=98=EA=B8=B0=20?= =?UTF-8?q?=EC=A0=84=20GOGO=20=EC=9D=B4=EB=AA=A8=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=EB=88=84=EB=A5=B8=20=EC=82=AC=EB=9E=8C=EB=93=A4=EC=9D=B4=20?= =?UTF-8?q?=EB=88=84=EB=9D=BD=EB=90=98=EB=8A=94=EC=A7=80=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=ED=95=98=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=20=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bigchat/test_create_bigchat_sheet.py | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/handler/bigchat/test_create_bigchat_sheet.py b/test/handler/bigchat/test_create_bigchat_sheet.py index 67a7a96..c91bd85 100644 --- a/test/handler/bigchat/test_create_bigchat_sheet.py +++ b/test/handler/bigchat/test_create_bigchat_sheet.py @@ -2,6 +2,8 @@ from unittest.mock import MagicMock from handler.bigchat.create_bigchat_sheet import CreateBigchatSheet +from implementation.slack_client import Reaction +from implementation.member_finder import Member, MemberManager from test.handler.bigchat.sample_data import create_sample_app_mention_event @@ -44,3 +46,33 @@ def test_not_run_by_sheet_name_notfound(self): mock_slack_client.send_message.assert_called_once() assert result is False assert "시트 이름이 입력되지 않았어. 다시 입력해줘!" in mock_slack_client.send_message.call_args.kwargs["msg"] + + def test_gogo_pressed_while_building_spreadsheet(self): + """빅챗 시트 생성이 완료되기 이전에 등록을 시도한(GOGO 이모지를 누른) + 인원들이 누락되지 않고 빅챗에 등록되었는지 확인합니다. + """ + event = create_sample_app_mention_event("<@U01BN035Y6L> 새로운 빅챗 빅챗 24-08-01") + mock_slack_client = MagicMock() + mock_slack_client.get_emoji.return_value = Reaction( + name="gogo", + users=["U01BN035Y6L"], + count=1, + ) + mock_gs_client = MagicMock() + mock_member_manager = MagicMock() + mock_member_manager.find.return_value = Member( + kor_name="김동주", + eng_name="Kim Dongjoo", + email="email", + phone="phone", + school_name_or_company_name="school_name_or_company_name", + ) + MemberManager.get_instance = MagicMock(return_value=mock_member_manager) + + sut = CreateBigchatSheet(event, mock_slack_client, mock_gs_client) + + assert sut.run() + + mock_member_manager.find.assert_called_once() + mock_slack_client.get_emoji.assert_called_once() + mock_gs_client.append_row.assert_called_once()