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: send action in kudos modal

+162 -8
+2 -2
kefi/constants.py
··· 23 23 LEAVE_MEET: str = "meet_leave" 24 24 SHOW_MEETS_MODAL: str = "show_meets_modal" 25 25 SHOW_GIVE_KUDOS_MODAL: str = "show_give_kudos_modal" 26 - USER_SELECT: str = "user_select" 27 - PLAIN_TEXT_INPUT: str = "plain_text_input" 26 + USER_SELECT: str = "action_user_receiver" 27 + PLAIN_TEXT_INPUT: str = "action_message" 28 28 29 29 30 30 class EventBodyType:
+2 -1
kefi/models/kudos/helpers.py
··· 2 2 3 3 from kefi.config import settings 4 4 from kefi.constants import Command 5 + from kefi.models.core.exceptions import NotEnoughKefi 5 6 from kefi.models.core.helpers import available_balance 6 7 from kefi.models.database import Action, Transaction, User 7 8 from kefi.routers.responses import ActionResponse ··· 58 59 # Checks sender wallet 59 60 balance = available_balance(user=sender, session=session) 60 61 if balance < action.amount: 61 - raise ValueError("The user doesn't have enough balance") 62 + raise NotEnoughKefi("The user doesn't have enough balance") 62 63 # Creates transactions 63 64 sender_transaction = Transaction( 64 65 action=action,
+37 -5
kefi/routers/helpers.py
··· 115 115 message=message, 116 116 session=self.session, 117 117 ) 118 - except ValueError: 118 + except NotEnoughKefi: 119 119 return SimpleResponse("¡No tienes suficientes kefis! 💸") 120 120 # Notify receiver in private chat 121 121 notify_receiver_user_chat_action( ··· 210 210 handler.handle() 211 211 return [] 212 212 213 - def _view_submission_meet_join(self, view_id: str) -> list | dict: 213 + def _view_submission_meet_join( 214 + self, view_id: str, state: dict | None = None 215 + ) -> list | dict: 214 216 """Joins the plaza.""" 215 217 message: BaseMessage 216 218 next_plaza = get_or_create_current_plaza(session=self.session) ··· 238 240 ) 239 241 return {"response_action": "clear"} 240 242 241 - def _view_submission_meet_leave(self, view_id: str) -> list | dict: 243 + def _view_submission_meet_leave( 244 + self, view_id: str, state: dict | None = None 245 + ) -> list | dict: 242 246 """Leaves the plaza.""" 243 247 message: BaseMessage 244 248 try: ··· 258 262 ) 259 263 return {"response_action": "clear"} 260 264 261 - def _view_submission_give_kudos(self, view_id: str) -> list | dict: 265 + def _view_submission_give_kudos( 266 + self, view_id: str, state: dict | None = None 267 + ) -> list | dict: 268 + if not state: 269 + return [] 270 + action = get_action(keyword=Command.KUDOS, session=self.session) 271 + if not action: 272 + return [] 273 + slack_user_id = state["values"]["receiver"][Actions.USER_SELECT][ 274 + "selected_user" 275 + ] 276 + message = state["values"]["message"][Actions.PLAIN_TEXT_INPUT]["value"] 277 + receiver = find_user_by_slack_user_id( 278 + slack_user_id=slack_user_id, session=self.session 279 + ) 280 + if not receiver: 281 + return [] 282 + try: 283 + send_action( 284 + sender=self.user, 285 + action=action, 286 + receiver=receiver, 287 + message=message, 288 + session=self.session, 289 + ) 290 + # TODO Notify receiver in private chat 291 + except NotEnoughKefi: 292 + ... 262 293 return {"response_action": "clear"} 263 294 264 295 def interaction_view_submission(self) -> list | dict: 265 296 """Handles the submissions from a view.""" 266 297 view_id = self.payload["view"]["id"] 267 298 view_callback_id = self.payload["view"]["callback_id"] 299 + state = self.payload["view"]["state"] 268 300 try: 269 301 return getattr(self, f"_view_submission_{view_callback_id}")( 270 - view_id=view_id 302 + view_id=view_id, state=state 271 303 ) 272 304 except AttributeError: 273 305 ...
+2
kefi/routers/views.py
··· 207 207 submit=PlainTextObject(text="Enviar Kefis"), 208 208 blocks=[ 209 209 SectionBlock( 210 + block_id="receiver", 210 211 text=MarkdownTextObject(text="¿A quien quieres felicitar?"), 211 212 accessory=UserSelectElement( 212 213 placeholder=PlainTextObject(text="Selecciona un usuario"), ··· 214 215 ), 215 216 ), 216 217 InputBlock( 218 + block_id="message", 217 219 element=PlainTextInputElement( 218 220 multiline=True, action_id=Actions.PLAIN_TEXT_INPUT 219 221 ),
+119
kefi/tests/test_interactivity.py
··· 3 3 from fastapi.testclient import TestClient 4 4 from pytest_mock import MockerFixture 5 5 from slack_sdk.web.client import WebClient 6 + from sqlmodel import Session 7 + 8 + from kefi.models.core.helpers import reset_wallets 9 + from kefi.models.database import User 6 10 7 11 8 12 def test_default_interactivity(client: TestClient, mocker: MockerFixture): ··· 190 194 ) 191 195 response = client.post("/interactivity/", data={"payload": payload}) 192 196 assert response.status_code == 200 197 + 198 + 199 + def test_view_action_kudos(client: TestClient, mocker: MockerFixture, session: Session): 200 + users = [User(slack_user_id="user_1"), User(slack_user_id="user_2")] 201 + for user in users: 202 + session.add(user) 203 + reset_wallets(session=session) 204 + views_open = mocker.patch.object(WebClient, "views_open") 205 + views_open_response: dict = {} # Adds expected response here 206 + views_open.side_effect = [views_open_response] 207 + payload = json.dumps( 208 + { 209 + "type": "view_submission", 210 + "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 211 + "user": { 212 + "id": "user_1", 213 + "username": "marcos", 214 + "name": "marcos", 215 + "team_id": "T0RS507QX", 216 + }, 217 + "api_app_id": "A0499FQH7V3", 218 + "token": "7n993LRqLRydOnIkEhymVcYD", 219 + "trigger_id": "4353633465297.25889007847.861312cf1a82b53e7b2962c17c74e6d3", 220 + "view": { 221 + "id": "V049LD8L17Z", 222 + "team_id": "T0RS507QX", 223 + "type": "modal", 224 + "blocks": [ 225 + { 226 + "type": "section", 227 + "block_id": "9ch", 228 + "text": { 229 + "type": "mrkdwn", 230 + "text": "¿A quien quieres felicitar?", 231 + "verbatim": False, 232 + }, 233 + "accessory": { 234 + "type": "users_select", 235 + "action_id": "user_select", 236 + "placeholder": { 237 + "type": "plain_text", 238 + "text": "Selecciona un usuario", 239 + "emoji": True, 240 + }, 241 + }, 242 + }, 243 + { 244 + "type": "input", 245 + "block_id": "V7=l", 246 + "label": { 247 + "type": "plain_text", 248 + "text": "Déjale un mensaje", 249 + "emoji": True, 250 + }, 251 + "optional": False, 252 + "dispatch_action": False, 253 + "element": { 254 + "type": "plain_text_input", 255 + "action_id": "plain_text_input", 256 + "multiline": True, 257 + "dispatch_action_config": { 258 + "trigger_actions_on": ["on_enter_pressed"] 259 + }, 260 + }, 261 + }, 262 + { 263 + "type": "context", 264 + "block_id": "1w6w", 265 + "elements": [ 266 + { 267 + "type": "plain_text", 268 + "text": "Valor: 100 Kefis", 269 + "emoji": True, 270 + } 271 + ], 272 + }, 273 + ], 274 + "private_metadata": "", 275 + "callback_id": "give_kudos", 276 + "state": { 277 + "values": { 278 + "receiver": { 279 + "action_user_receiver": { 280 + "type": "users_select", 281 + "selected_user": "user_2", 282 + } 283 + }, 284 + "message": { 285 + "action_message": { 286 + "type": "plain_text_input", 287 + "value": "dummy", 288 + } 289 + }, 290 + } 291 + }, 292 + "hash": "1667928027.cHsXEKGC", 293 + "title": {"type": "plain_text", "text": "¡Gracias!", "emoji": True}, 294 + "clear_on_close": False, 295 + "notify_on_close": False, 296 + "close": {"type": "plain_text", "text": "Cancelar", "emoji": True}, 297 + "submit": {"type": "plain_text", "text": "Enviar Kefis", "emoji": True}, 298 + "previous_view_id": None, 299 + "root_view_id": "V049LD8L17Z", 300 + "app_id": "A0499FQH7V3", 301 + "external_id": "", 302 + "app_installed_team_id": "T0RS507QX", 303 + "bot_id": "B0495T0Q0S2", 304 + }, 305 + "response_urls": [], 306 + "is_enterprise_install": False, 307 + "enterprise": None, 308 + } 309 + ) 310 + response = client.post("/interactivity/", data={"payload": payload}) 311 + assert response.status_code == 200