Python backend for a Slack's kudos plugin.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: join and leave views

+132 -14
+21
kefi/models/core/helpers.py
··· 24 24 return (user, created) 25 25 26 26 27 + def get_or_create_from_interactivity( 28 + interactivity_payload: dict, session: Session 29 + ) -> tuple["User", bool]: 30 + """Gets or create the user from the given command.""" 31 + query = select(User).filter( 32 + User.slack_user_id == interactivity_payload["user"]["id"] 33 + ) 34 + user = session.exec(query).one_or_none() 35 + created = not user 36 + if not user: 37 + user = User( 38 + slack_user_id=interactivity_payload["user"]["id"], 39 + slack_username=interactivity_payload["user"]["username"], 40 + ) 41 + else: 42 + # Updates the slack username 43 + user.slack_username = interactivity_payload["user"]["username"] 44 + session.add(user) 45 + return (user, created) 46 + 47 + 27 48 def available_balance(user: User, session: Session) -> int: 28 49 """Obtains the available balance of the user, that means the balance not spend from 29 50 the monthly received.
+40 -12
kefi/routers/helpers.py
··· 11 11 find_user_by_slack_user_id, 12 12 get_all_users, 13 13 get_or_create_from_command, 14 + get_or_create_from_interactivity, 14 15 received_balance, 15 16 send_admin_amount, 16 17 ) ··· 19 20 notify_receiver_user_chat_action, 20 21 send_action, 21 22 ) 23 + from kefi.models.plazas.helpers import create_attendance, is_attending 22 24 from kefi.routers.responses import ( 23 25 ActionResponse, 24 26 HelpResponse, ··· 147 149 148 150 class InteractionHandler: 149 151 def __init__(self, interaction: InteractionParams, session: Session): 150 - print(interaction.payload) 152 + self.session = session 151 153 self.payload = json.loads(interaction.payload) 154 + self.user, _ = get_or_create_from_interactivity( 155 + interactivity_payload=self.payload, session=session 156 + ) 152 157 self.slack = Slack() 153 158 154 - def response(self): 159 + def response(self) -> SlackResponse: 155 160 handlers: dict[str, Callable] = { 156 161 InteractionType.SHORTCUT: self.interaction_shortcut, 157 162 InteractionType.BLOCK_ACTIONS: self.interaction_block_actions, 158 163 InteractionType.VIEW_SUBMISSION: self.interaction_view_submission, 159 164 } 160 - type = self.payload["type"] 161 - response = handlers.get(type, self.not_found)(payload=self.payload) 165 + _type = self.payload["type"] 166 + response = handlers.get(_type, self.not_found)() 162 167 return response 163 168 164 - def interaction_shortcut(self, *args, **kwargs): 169 + def interaction_shortcut(self) -> list: 170 + """First interaction with the shortcut. Shows a view depending if the user is 171 + in the plaza or not. 172 + 173 + Important, we assume all the interactivity comes from `kefi_meets`, so, 174 + if we add more shortcuts, we need to check `callback_id`. 175 + """ 165 176 trigger_id = self.payload["trigger_id"] 166 - self.slack.open_view(trigger_id=trigger_id, view=JoinMeetView()) 177 + if not is_attending(user=self.user, session=self.session): 178 + self.slack.open_view( 179 + trigger_id=trigger_id, view=JoinMeetView(session=self.session) 180 + ) 181 + else: 182 + self.slack.open_view( 183 + trigger_id=trigger_id, view=LeaveMeetView(session=self.session) 184 + ) 167 185 return [] 168 186 169 - def interaction_block_actions(self, *args, **kwargs): 170 - view_id = self.payload["view"]["id"] 171 - self.slack.update_view(view_id=view_id, view=LeaveMeetView()) 187 + def _block_action_meet_join(self, view_id: str): 188 + """Joins the plaza.""" 189 + try: 190 + create_attendance(user=self.user, session=self.session) 191 + except ValueError: 192 + ... 193 + 194 + def interaction_block_actions(self) -> list: 172 195 return [] 173 196 174 - def interaction_view_submission(self, *args, **kwargs): 175 - print("im here") 197 + def interaction_view_submission(self) -> list: 198 + view_id = self.payload["view"]["id"] 199 + view_callback_id = self.payload["view"]["callback_id"] 200 + try: 201 + getattr(self, f"_block_action_{view_callback_id}")(view_id=view_id) 202 + except AttributeError: 203 + ... 176 204 return [] 177 205 178 - def not_found(self, *args, **kwargs): 206 + def not_found(self) -> list: 179 207 return []
+7 -2
kefi/routers/views.py
··· 11 11 from slack_sdk.models.views import View 12 12 13 13 from kefi.constants import ViewType 14 + from kefi.models.plazas.helpers import get_or_create_current_plaza 14 15 15 16 16 17 class JoinMeetView(View): 17 18 def __init__(self, *args, **kwargs): 19 + session = kwargs.get("session") 20 + next_plaza = get_or_create_current_plaza(session=session) 18 21 super().__init__( 19 22 callback_id=ViewType.JOIN_MEET, 20 23 type="modal", ··· 30 33 DividerBlock(), 31 34 SectionBlock( 32 35 text=MarkdownTextObject( 33 - text="*Próximo encuentro*\nViernes, 11 Noviembre \n10:00-10:30am\n" 36 + text=f"*Próximo encuentro*\n{next_plaza.date.strftime('%A, %-d %B')}\n{next_plaza.date.strftime('%H:%M')}\n" 34 37 ), 35 38 accessory=ImageElement( 36 39 image_url="https://api.slack.com/img/blocks/bkb_template_images/notifications.png", ··· 43 46 44 47 class LeaveMeetView(View): 45 48 def __init__(self, *args, **kwargs): 49 + session = kwargs.get("session") 50 + next_plaza = get_or_create_current_plaza(session=session) 46 51 super().__init__( 47 52 callback_id=ViewType.LEAVE_MEET, 48 53 type="modal", ··· 58 63 DividerBlock(), 59 64 SectionBlock( 60 65 text=MarkdownTextObject( 61 - text="*Kefi Plaza*\nViernes, 11 Noviembre \n10:00-10:30am\n" 66 + text=f"*Kefi Plaza*\n{next_plaza.date.strftime('%A, %-d %B')}\n{next_plaza.date.strftime('%H:%M')}\n" 62 67 ), 63 68 accessory=ImageElement( 64 69 image_url="https://api.slack.com/img/blocks/bkb_template_images/notifications.png",
+64
kefi/tests/test_interactivity.py
··· 35 35 }, 36 36 ) 37 37 assert response.status_code == 200 38 + 39 + 40 + { 41 + "type": "view_submission", 42 + "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 43 + "user": { 44 + "id": "UCBKX2GQ4", 45 + "username": "marcos", 46 + "name": "marcos", 47 + "team_id": "T0RS507QX", 48 + }, 49 + "api_app_id": "A0499FQH7V3", 50 + "token": "7n993LRqLRydOnIkEhymVcYD", 51 + "trigger_id": "4338017757538.25889007847.877c730e79e8a4b1917de09d38a8daa0", 52 + "view": { 53 + "id": "V04AMLW580G", 54 + "team_id": "T0RS507QX", 55 + "type": "modal", 56 + "blocks": [ 57 + { 58 + "type": "section", 59 + "block_id": "9n+9", 60 + "text": { 61 + "type": "mrkdwn", 62 + "text": "*:wave: \\u00a1Hola! \\u00bfTe vienes a la Kefi Plaza?*", 63 + "verbatim": False, 64 + }, 65 + }, 66 + {"type": "divider", "block_id": "MSUNF"}, 67 + { 68 + "type": "section", 69 + "block_id": "IbY", 70 + "text": { 71 + "type": "mrkdwn", 72 + "text": "*Pr\\u00f3ximo encuentro*\\nFriday, 11 November\\n10:00\\n", 73 + "verbatim": False, 74 + }, 75 + "accessory": { 76 + "type": "image", 77 + "image_url": "https:\\/\\/api.slack.com\\/img\\/blocks\\/bkb_template_images\\/notifications.png", 78 + "alt_text": "calendar thumbnail", 79 + }, 80 + }, 81 + ], 82 + "private_metadata": "", 83 + "callback_id": "meet_join", 84 + "state": {"values": {}}, 85 + "hash": "1667896295.Ry45BkuV", 86 + "title": {"type": "plain_text", "text": "Kefi", "emoji": True}, 87 + "clear_on_close": False, 88 + "notify_on_close": False, 89 + "close": {"type": "plain_text", "text": "Cancelar", "emoji": True}, 90 + "submit": {"type": "plain_text", "text": "\\u00a1Me apunto!", "emoji": True}, 91 + "previous_view_id": None, 92 + "root_view_id": "V04AMLW580G", 93 + "app_id": "A0499FQH7V3", 94 + "external_id": "", 95 + "app_installed_team_id": "T0RS507QX", 96 + "bot_id": "B0495T0Q0S2", 97 + }, 98 + "response_urls": [], 99 + "is_enterprise_install": False, 100 + "enterprise": None, 101 + }