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

Configure Feed

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

Merge pull request #10 from Dekalabs/feature/kefi_modals

Feature/kefi modals

authored by

Marcos Gabarda and committed by
GitHub
8916b68f 9394f608

+829 -175
+15 -2
kefi/constants.py
··· 16 16 class ViewType: 17 17 JOIN_MEET: str = "meet_join" 18 18 LEAVE_MEET: str = "meet_leave" 19 + GIVE_KUDOS: str = "give_kudos" 20 + GIVE_CONGRATS: str = "give_congrats" 21 + GIVE_HIGH_FIVE: str = "give_high_five" 19 22 20 23 21 24 class Actions: 22 - LEAVE_MEET: str = "meet_leave" 23 - SHOW_MEETS_MODAL: str = "show_meets_modal" 25 + LEAVE_MEET: str = "action_meet_leave" 26 + SHOW_MEETS_MODAL: str = "action_show_meets_modal" 27 + SHOW_GIVE_KUDOS_MODAL: str = "action_show_give_kudos_modal" 28 + SHOW_GIVE_CONGRATS_MODAL: str = "action_show_give_congrats_modal" 29 + SHOW_GIVE_HIGH_FIVE_MODAL: str = "action_show_give_high_five_modal" 30 + USER_SELECT: str = "action_user_receiver" 31 + PLAIN_TEXT_INPUT: str = "action_message" 24 32 25 33 26 34 class EventBodyType: 27 35 URL_VERIFICATION: str = "url_verification" 28 36 EVENT_CALLBACK: str = "event_callback" 37 + 38 + 39 + class Block: 40 + RECEIVER: str = "block_receiver" 41 + MESSAGE: str = "block_message"
+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,
+252 -8
kefi/routers/helpers.py
··· 4 4 5 5 from sqlmodel import Session 6 6 7 - from kefi.constants import Actions, Command, EventBodyType, InteractionType 7 + from kefi.config import settings 8 + from kefi.constants import Actions, Block, Command, EventBodyType, InteractionType 8 9 from kefi.dependencies import EventBody, InteractionParams, SlashCommandParams 9 10 from kefi.models.core.exceptions import NotEnoughKefi 10 11 from kefi.models.core.helpers import ( ··· 33 34 from kefi.routers.messages import ( 34 35 AlreadyAttendingMessage, 35 36 BaseMessage, 37 + CongratsSentMessage, 38 + HighFiveSentMessage, 39 + KudosSentMessage, 36 40 NotAttendingMessage, 37 - NotEnoughKefiMessage, 41 + NotEnoughKefiToGiveMessage, 42 + NotEnoughKefiToJoinMessage, 43 + NotGiveToYourselfMessage, 44 + NotUserToGiveMessage, 38 45 UserJoinedMeetMessage, 39 46 UserLeftMeetMessage, 40 47 ) ··· 46 53 SlackResponse, 47 54 WalletResponse, 48 55 ) 49 - from kefi.routers.views import HomeView, JoinMeetView, LeaveMeetView 56 + from kefi.routers.views import ( 57 + GiveCongratsView, 58 + GiveHighFiveView, 59 + GiveKudosView, 60 + HomeView, 61 + JoinMeetView, 62 + LeaveMeetView, 63 + ) 50 64 from kefi.slack import Slack 51 65 52 66 ··· 115 129 message=message, 116 130 session=self.session, 117 131 ) 118 - except ValueError: 132 + except NotEnoughKefi: 119 133 return SimpleResponse("¡No tienes suficientes kefis! 💸") 120 134 # Notify receiver in private chat 121 135 notify_receiver_user_chat_action( ··· 210 224 handler.handle() 211 225 return [] 212 226 213 - def _view_submission_meet_join(self, view_id: str) -> list | dict: 227 + def _view_submission_meet_join( 228 + self, view_id: str, state: dict | None = None 229 + ) -> list | dict: 214 230 """Joins the plaza.""" 215 231 message: BaseMessage 216 232 next_plaza = get_or_create_current_plaza(session=self.session) ··· 223 239 text=message.text(), 224 240 ) 225 241 except NotEnoughKefi: 226 - message = NotEnoughKefiMessage() 242 + message = NotEnoughKefiToJoinMessage() 227 243 self.slack.post_message_user( 228 244 user_id=self.user.slack_user_id, 229 245 blocks=message.blocks(), ··· 238 254 ) 239 255 return {"response_action": "clear"} 240 256 241 - def _view_submission_meet_leave(self, view_id: str) -> list | dict: 257 + def _view_submission_meet_leave( 258 + self, view_id: str, state: dict | None = None 259 + ) -> list | dict: 242 260 """Leaves the plaza.""" 243 261 message: BaseMessage 244 262 try: ··· 258 276 ) 259 277 return {"response_action": "clear"} 260 278 279 + def _view_submission_give_kudos( 280 + self, view_id: str, state: dict | None = None 281 + ) -> list | dict: 282 + if not state: 283 + return [] 284 + action = get_action(keyword=Command.KUDOS, session=self.session) 285 + if not action: 286 + return [] 287 + slack_user_id = state["values"][Block.RECEIVER][Actions.USER_SELECT][ 288 + "selected_user" 289 + ] 290 + 291 + message = state["values"][Block.MESSAGE][Actions.PLAIN_TEXT_INPUT]["value"] 292 + receiver = find_user_by_slack_user_id( 293 + slack_user_id=slack_user_id, session=self.session 294 + ) 295 + 296 + if not receiver: 297 + not_user_to_give = NotUserToGiveMessage() 298 + self.slack.post_message_user( 299 + user_id=self.user.slack_user_id, 300 + blocks=not_user_to_give.blocks(), 301 + text=not_user_to_give.text(), 302 + ) 303 + return {"response_action": "clear"} 304 + 305 + if self.user.slack_user_id == receiver.id: 306 + not_give_to_yourself_message = NotGiveToYourselfMessage() 307 + self.slack.post_message_user( 308 + user_id=self.user.slack_user_id, 309 + blocks=not_give_to_yourself_message.blocks(), 310 + text=not_give_to_yourself_message.text(), 311 + ) 312 + return {"response_action": "clear"} 313 + 314 + try: 315 + send_action( 316 + sender=self.user, 317 + action=action, 318 + receiver=receiver, 319 + message=message, 320 + session=self.session, 321 + ) 322 + 323 + sent_message = KudosSentMessage( 324 + sender=self.user, 325 + receiver=receiver, 326 + message=message, 327 + amount=settings.KUDOS_PRICE, 328 + ) 329 + self.slack.post_message_user( 330 + user_id=self.user.slack_user_id, 331 + blocks=sent_message.blocks(), 332 + text=sent_message.text(), 333 + ) 334 + 335 + self.slack.post_message_user( 336 + user_id=receiver.slack_user_id, 337 + blocks=sent_message.blocks(), 338 + text=sent_message.text(), 339 + ) 340 + except NotEnoughKefi: 341 + not_enough_message = NotEnoughKefiToGiveMessage() 342 + self.slack.post_message_user( 343 + user_id=self.user.slack_user_id, 344 + blocks=not_enough_message.blocks(), 345 + text=not_enough_message.text(), 346 + ) 347 + return {"response_action": "clear"} 348 + 349 + def _view_submission_give_congrats( 350 + self, view_id: str, state: dict | None = None 351 + ) -> list | dict: 352 + if not state: 353 + return [] 354 + action = get_action(keyword=Command.CONGRATS, session=self.session) 355 + if not action: 356 + return [] 357 + slack_user_id = state["values"][Block.RECEIVER][Actions.USER_SELECT][ 358 + "selected_user" 359 + ] 360 + 361 + message = state["values"][Block.MESSAGE][Actions.PLAIN_TEXT_INPUT]["value"] 362 + receiver = find_user_by_slack_user_id( 363 + slack_user_id=slack_user_id, session=self.session 364 + ) 365 + 366 + if not receiver: 367 + not_user_to_give = NotUserToGiveMessage() 368 + self.slack.post_message_user( 369 + user_id=self.user.slack_user_id, 370 + blocks=not_user_to_give.blocks(), 371 + text=not_user_to_give.text(), 372 + ) 373 + return {"response_action": "clear"} 374 + 375 + if self.user.slack_user_id == receiver.id: 376 + not_give_to_yourself_message = NotGiveToYourselfMessage() 377 + self.slack.post_message_user( 378 + user_id=self.user.slack_user_id, 379 + blocks=not_give_to_yourself_message.blocks(), 380 + text=not_give_to_yourself_message.text(), 381 + ) 382 + return {"response_action": "clear"} 383 + 384 + try: 385 + send_action( 386 + sender=self.user, 387 + action=action, 388 + receiver=receiver, 389 + message=message, 390 + session=self.session, 391 + ) 392 + 393 + sent_message = CongratsSentMessage( 394 + sender=self.user, 395 + receiver=receiver, 396 + message=message, 397 + amount=settings.CONGRATS_PRICE, 398 + ) 399 + self.slack.post_message_user( 400 + user_id=self.user.slack_user_id, 401 + blocks=sent_message.blocks(), 402 + text=sent_message.text(), 403 + ) 404 + 405 + self.slack.post_message_user( 406 + user_id=receiver.slack_user_id, 407 + blocks=sent_message.blocks(), 408 + text=sent_message.text(), 409 + ) 410 + except NotEnoughKefi: 411 + not_enough_message = NotEnoughKefiToGiveMessage() 412 + self.slack.post_message_user( 413 + user_id=self.user.slack_user_id, 414 + blocks=not_enough_message.blocks(), 415 + text=not_enough_message.text(), 416 + ) 417 + return {"response_action": "clear"} 418 + 419 + def _view_submission_give_high_five( 420 + self, view_id: str, state: dict | None = None 421 + ) -> list | dict: 422 + if not state: 423 + return [] 424 + action = get_action(keyword=Command.HIGH_FIVE, session=self.session) 425 + if not action: 426 + return [] 427 + slack_user_id = state["values"][Block.RECEIVER][Actions.USER_SELECT][ 428 + "selected_user" 429 + ] 430 + 431 + message = state["values"][Block.MESSAGE][Actions.PLAIN_TEXT_INPUT]["value"] 432 + receiver = find_user_by_slack_user_id( 433 + slack_user_id=slack_user_id, session=self.session 434 + ) 435 + 436 + if not receiver: 437 + not_user_to_give = NotUserToGiveMessage() 438 + self.slack.post_message_user( 439 + user_id=self.user.slack_user_id, 440 + blocks=not_user_to_give.blocks(), 441 + text=not_user_to_give.text(), 442 + ) 443 + return {"response_action": "clear"} 444 + 445 + if self.user.slack_user_id == receiver.id: 446 + not_give_to_yourself_message = NotGiveToYourselfMessage() 447 + self.slack.post_message_user( 448 + user_id=self.user.slack_user_id, 449 + blocks=not_give_to_yourself_message.blocks(), 450 + text=not_give_to_yourself_message.text(), 451 + ) 452 + return {"response_action": "clear"} 453 + 454 + try: 455 + send_action( 456 + sender=self.user, 457 + action=action, 458 + receiver=receiver, 459 + message=message, 460 + session=self.session, 461 + ) 462 + 463 + sent_message = HighFiveSentMessage( 464 + sender=self.user, 465 + receiver=receiver, 466 + message=message, 467 + amount=settings.HIGH_FIVE_PRICE, 468 + ) 469 + self.slack.post_message_user( 470 + user_id=self.user.slack_user_id, 471 + blocks=sent_message.blocks(), 472 + text=sent_message.text(), 473 + ) 474 + 475 + self.slack.post_message_user( 476 + user_id=receiver.slack_user_id, 477 + blocks=sent_message.blocks(), 478 + text=sent_message.text(), 479 + ) 480 + except NotEnoughKefi: 481 + not_enough_message = NotEnoughKefiToGiveMessage() 482 + self.slack.post_message_user( 483 + user_id=self.user.slack_user_id, 484 + blocks=not_enough_message.blocks(), 485 + text=not_enough_message.text(), 486 + ) 487 + return {"response_action": "clear"} 488 + 261 489 def interaction_view_submission(self) -> list | dict: 262 490 """Handles the submissions from a view.""" 263 491 view_id = self.payload["view"]["id"] 264 492 view_callback_id = self.payload["view"]["callback_id"] 493 + state = self.payload["view"]["state"] 265 494 try: 266 495 return getattr(self, f"_view_submission_{view_callback_id}")( 267 - view_id=view_id 496 + view_id=view_id, state=state 268 497 ) 269 498 except AttributeError: 270 499 ... ··· 288 517 handlers: dict[str, Callable] = { 289 518 Actions.LEAVE_MEET: self.action_leave_meet, 290 519 Actions.SHOW_MEETS_MODAL: self.action_show_meets_modal, 520 + Actions.SHOW_GIVE_KUDOS_MODAL: self.action_show_give_kudos_modal, 521 + Actions.SHOW_GIVE_CONGRATS_MODAL: self.action_show_give_congrats_modal, 522 + Actions.SHOW_GIVE_HIGH_FIVE_MODAL: self.action_show_give_high_five_modal, 291 523 } 292 524 handlers.get(action["action_id"], self.not_found)() 293 525 ··· 323 555 self.slack.open_view( 324 556 trigger_id=trigger_id, view=LeaveMeetView(session=self.session) 325 557 ) 558 + 559 + def action_show_give_kudos_modal(self): 560 + trigger_id = self.payload["trigger_id"] 561 + self.slack.open_view(trigger_id=trigger_id, view=GiveKudosView()) 562 + 563 + def action_show_give_congrats_modal(self): 564 + trigger_id = self.payload["trigger_id"] 565 + self.slack.open_view(trigger_id=trigger_id, view=GiveCongratsView()) 566 + 567 + def action_show_give_high_five_modal(self): 568 + trigger_id = self.payload["trigger_id"] 569 + self.slack.open_view(trigger_id=trigger_id, view=GiveHighFiveView()) 326 570 327 571 328 572 class EventHandler:
+148 -2
kefi/routers/messages.py
··· 1 1 from slack_sdk.models.blocks import ( 2 2 Block, 3 3 ButtonElement, 4 + ContextBlock, 4 5 DividerBlock, 6 + HeaderBlock, 5 7 ImageElement, 6 8 MarkdownTextObject, 7 9 PlainTextObject, ··· 9 11 ) 10 12 11 13 from kefi.constants import Actions 12 - from kefi.models.database import Plaza 14 + from kefi.models.database import Plaza, User 13 15 14 16 15 17 class BaseMessage: ··· 70 72 ] 71 73 72 74 73 - class NotEnoughKefiMessage(BaseMessage): 75 + class NotEnoughKefiToJoinMessage(BaseMessage): 74 76 def text(self) -> str: 75 77 return ":money_with_wings: ¡Vaya! No tienes suficientes kefis para apuntarte." 76 78 ··· 110 112 ) 111 113 ) 112 114 ] 115 + 116 + 117 + class KudosSentMessage(BaseMessage): 118 + def __init__(self, sender: User, receiver: User, message: str, amount: int): 119 + self.sender_name = sender.get_short_name() 120 + self.receiver_name = receiver.get_short_name() 121 + self.message = message 122 + self.amount = amount 123 + 124 + def text(self) -> str: 125 + return f"{self.sender_name} le da las gracias a {self.receiver_name}" 126 + 127 + def blocks(self) -> list[Block]: 128 + return [ 129 + HeaderBlock(text=f"¡Gracias {self.receiver_name}!"), 130 + SectionBlock( 131 + text=MarkdownTextObject( 132 + text=f"Mensaje de {self.sender_name}:\n{self.message}" 133 + ), 134 + accessory=ImageElement( 135 + image_url="https://storage.staging.dekaside.com/kefi/static/images/kudos_400.png", 136 + alt_text="Kudos", 137 + ), 138 + ), 139 + ContextBlock( 140 + elements=[ 141 + ImageElement( 142 + image_url="https://storage.staging.dekaside.com/kefi/static/images/coin_70.png", 143 + alt_text="Kefis", 144 + ), 145 + MarkdownTextObject( 146 + text=f"*{self.sender_name}* le da a *{self.receiver_name}* {self.amount} kefis." 147 + ), 148 + ] 149 + ), 150 + ] 151 + 152 + 153 + class CongratsSentMessage(BaseMessage): 154 + def __init__(self, sender: User, receiver: User, message: str, amount: int): 155 + self.sender_name = sender.get_short_name() 156 + self.receiver_name = receiver.get_short_name() 157 + self.message = message 158 + self.amount = amount 159 + 160 + def text(self) -> str: 161 + return f"{self.sender_name} le da la enhorabuena a {self.receiver_name}" 162 + 163 + def blocks(self) -> list[Block]: 164 + return [ 165 + HeaderBlock(text=f"¡Enhorabuena {self.receiver_name}!"), 166 + SectionBlock( 167 + text=MarkdownTextObject( 168 + text=f"Mensaje de {self.sender_name}:\n{self.message}" 169 + ), 170 + accessory=ImageElement( 171 + image_url="https://storage.staging.dekaside.com/kefi/static/images/congrats_400.png", 172 + alt_text="Kudos", 173 + ), 174 + ), 175 + ContextBlock( 176 + elements=[ 177 + ImageElement( 178 + image_url="https://storage.staging.dekaside.com/kefi/static/images/coin_70.png", 179 + alt_text="Kefis", 180 + ), 181 + MarkdownTextObject( 182 + text=f"*{self.sender_name}* le da a *{self.receiver_name}* {self.amount} kefis." 183 + ), 184 + ] 185 + ), 186 + ] 187 + 188 + 189 + class HighFiveSentMessage(BaseMessage): 190 + def __init__(self, sender: User, receiver: User, message: str, amount: int): 191 + self.sender_name = sender.get_short_name() 192 + self.receiver_name = receiver.get_short_name() 193 + self.message = message 194 + self.amount = amount 195 + 196 + def text(self) -> str: 197 + return f"{self.sender_name} le envia un high five a {self.receiver_name}" 198 + 199 + def blocks(self) -> list[Block]: 200 + return [ 201 + HeaderBlock( 202 + text=f"¡{self.sender_name} le envía un high five a {self.receiver_name}!" 203 + ), 204 + SectionBlock( 205 + text=MarkdownTextObject( 206 + text=f"Mensaje de {self.sender_name}:\n{self.message}" 207 + ), 208 + accessory=ImageElement( 209 + image_url="https://storage.staging.dekaside.com/kefi/static/images/highfive_400.png", 210 + alt_text="Kudos", 211 + ), 212 + ), 213 + ContextBlock( 214 + elements=[ 215 + ImageElement( 216 + image_url="https://storage.staging.dekaside.com/kefi/static/images/coin_70.png", 217 + alt_text="Kefis", 218 + ), 219 + MarkdownTextObject( 220 + text=f"*{self.sender_name}* le da a *{self.receiver_name}* {self.amount} kefis." 221 + ), 222 + ] 223 + ), 224 + ] 225 + 226 + 227 + class NotEnoughKefiToGiveMessage(BaseMessage): 228 + def text(self) -> str: 229 + return "¡No tienes suficientes kefis! 💸" 230 + 231 + def blocks(self) -> list[Block]: 232 + return [ 233 + SectionBlock( 234 + text=MarkdownTextObject(text="¡No tienes suficientes kefis! 💸") 235 + ) 236 + ] 237 + 238 + 239 + class NotGiveToYourselfMessage(BaseMessage): 240 + def text(self) -> str: 241 + return "¡No te puedes dar kefis! 🙅" 242 + 243 + def blocks(self) -> list[Block]: 244 + return [ 245 + SectionBlock(text=MarkdownTextObject(text="¡No te puedes dar kefis! 🙅")) 246 + ] 247 + 248 + 249 + class NotUserToGiveMessage(BaseMessage): 250 + def text(self) -> str: 251 + return "No se ha especificado un destinatario" 252 + 253 + def blocks(self) -> list[Block]: 254 + return [ 255 + SectionBlock( 256 + text=MarkdownTextObject(text="No se ha especificado un destinatario") 257 + ) 258 + ]
+133 -7
kefi/routers/views.py
··· 5 5 DividerBlock, 6 6 HeaderBlock, 7 7 ImageElement, 8 + InputBlock, 8 9 MarkdownTextObject, 10 + PlainTextInputElement, 9 11 PlainTextObject, 10 12 SectionBlock, 13 + UserSelectElement, 11 14 ) 12 15 from slack_sdk.models.views import View 13 16 14 17 from kefi.config import settings 15 - from kefi.constants import Actions, ViewType 18 + from kefi.constants import Actions, Block, ViewType 16 19 from kefi.models.plazas.helpers import get_or_create_current_plaza 17 20 18 21 ··· 46 49 ContextBlock( 47 50 elements=[ 48 51 PlainTextObject( 49 - text=f"Al apuntarte consumirás {settings.PLAZA_PRICE} Kefis." 52 + text=f"Al apuntarte consumirás {settings.PLAZA_PRICE} kefis." 50 53 ) 51 54 ] 52 55 ), ··· 106 109 HeaderBlock(text=PlainTextObject(text="¡Gracias!")), 107 110 SectionBlock( 108 111 text=MarkdownTextObject( 109 - text=f"Hazle llegar tu agradecimiento a un compañero por ayudarte en un proyecto.\nValor: {settings.KUDOS_PRICE} Kefis" 112 + text=f"Hazle llegar tu agradecimiento a un compañero por ayudarte en un proyecto.\n*Valor:* {settings.KUDOS_PRICE} kefis" 110 113 ), 111 114 accessory=ImageElement( 112 115 image_url="https://storage.staging.dekaside.com/kefi/static/images/kudos.png", 113 116 alt_text="Kudos", 114 117 ), 115 118 ), 119 + ActionsBlock( 120 + elements=[ 121 + ButtonElement( 122 + text=PlainTextObject(text="Dar las gracias"), 123 + action_id=Actions.SHOW_GIVE_KUDOS_MODAL, 124 + ) 125 + ] 126 + ), 116 127 ContextBlock( 117 128 elements=[PlainTextObject(text="/kefi kudos @user [mensaje]")] 118 129 ), ··· 120 131 HeaderBlock(text=PlainTextObject(text="¡Enhorabuena!")), 121 132 SectionBlock( 122 133 text=MarkdownTextObject( 123 - text=f"Un trabajo bien hecho, una buena idea, la certificación en un nuevo curso... ¡se merecen una enhorabuena!\nValor: {settings.CONGRATS_PRICE} Kefis" 134 + text=f"Un trabajo bien hecho, una buena idea, la certificación en un nuevo curso... ¡se merecen una enhorabuena!\n*Valor:* {settings.CONGRATS_PRICE} kefis" 124 135 ), 125 136 accessory=ImageElement( 126 137 image_url="https://storage.staging.dekaside.com/kefi/static/images/congrats.png", 127 138 alt_text="Congrats", 128 139 ), 140 + ), 141 + ActionsBlock( 142 + elements=[ 143 + ButtonElement( 144 + text=PlainTextObject(text="Dar la enhorabuena"), 145 + action_id=Actions.SHOW_GIVE_CONGRATS_MODAL, 146 + ) 147 + ] 129 148 ), 130 149 ContextBlock( 131 150 elements=[PlainTextObject(text="/kefi congrats @user [mensaje]")] ··· 134 153 HeaderBlock(text=PlainTextObject(text="¡High Five!")), 135 154 SectionBlock( 136 155 text=MarkdownTextObject( 137 - text=f"Alguien ha sido un buen colega y te ha alegrado el día.\nValor: {settings.HIGH_FIVE_PRICE} Kefis" 156 + text=f"Alguien ha sido un buen colega y te ha alegrado el día.\n*Valor:* {settings.HIGH_FIVE_PRICE} kefis" 138 157 ), 139 158 accessory=ImageElement( 140 159 image_url="https://storage.staging.dekaside.com/kefi/static/images/highfive.png", 141 160 alt_text="High five", 142 161 ), 143 162 ), 163 + ActionsBlock( 164 + elements=[ 165 + ButtonElement( 166 + text=PlainTextObject(text="Chocar los cinco"), 167 + action_id=Actions.SHOW_GIVE_HIGH_FIVE_MODAL, 168 + ) 169 + ] 170 + ), 144 171 ContextBlock( 145 172 elements=[PlainTextObject(text="/kefi highfive @user [mensaje]")] 146 173 ), ··· 148 175 HeaderBlock(text=PlainTextObject(text="Kefi Plaza")), 149 176 SectionBlock( 150 177 text=MarkdownTextObject( 151 - text=f"Cada encuentro se asignan aleatoriamente grupos de {settings.PLAZA_SIZE} personas en una sala virtual para compartir un Kefi online y hablar de los que más os guste. ¿Te unes?\nCoste: {settings.PLAZA_PRICE} Kefis" 178 + text=f"Cada encuentro se asignan aleatoriamente grupos de {settings.PLAZA_SIZE} personas en una sala virtual para compartir un Kefi online y hablar de los que más os guste. ¿Te unes?\nCoste: {settings.PLAZA_PRICE} k" 152 179 ), 153 180 accessory=ImageElement( 154 181 image_url="https://storage.staging.dekaside.com/kefi/static/images/kefi_plaza.png", ··· 158 185 ActionsBlock( 159 186 elements=[ 160 187 ButtonElement( 161 - text=PlainTextObject(text="¡Me Apunto!"), 188 + text=PlainTextObject(text="¡Me apunto!"), 162 189 action_id=Actions.SHOW_MEETS_MODAL, 163 190 style="primary", 164 191 ) ··· 182 209 ContextBlock(elements=[PlainTextObject(text="/kefi help")]), 183 210 ], 184 211 ) 212 + 213 + 214 + class GiveKudosView(View): 215 + def __init__(self, *args, **kwargs): 216 + super().__init__( 217 + callback_id=ViewType.GIVE_KUDOS, 218 + type="modal", 219 + title=PlainTextObject(text="¡Gracias!"), 220 + close=PlainTextObject(text="Cancelar"), 221 + submit=PlainTextObject(text="Dar las gracias"), 222 + blocks=[ 223 + SectionBlock( 224 + block_id=Block.RECEIVER, 225 + text=MarkdownTextObject(text="¿A quien quieres felicitar?"), 226 + accessory=UserSelectElement( 227 + placeholder=PlainTextObject(text="Selecciona un usuario"), 228 + action_id=Actions.USER_SELECT, 229 + ), 230 + ), 231 + InputBlock( 232 + block_id=Block.MESSAGE, 233 + element=PlainTextInputElement( 234 + multiline=True, action_id=Actions.PLAIN_TEXT_INPUT 235 + ), 236 + label="Déjale un mensaje", 237 + ), 238 + ContextBlock( 239 + elements=[ 240 + PlainTextObject(text=f"Valor: {settings.KUDOS_PRICE} kefis") 241 + ] 242 + ), 243 + ], 244 + ) 245 + 246 + 247 + class GiveCongratsView(View): 248 + def __init__(self, *args, **kwargs): 249 + super().__init__( 250 + callback_id=ViewType.GIVE_CONGRATS, 251 + type="modal", 252 + title=PlainTextObject(text="¡Enhorabuena!"), 253 + close=PlainTextObject(text="Cancelar"), 254 + submit=PlainTextObject(text="Dar la enhorabuena"), 255 + blocks=[ 256 + SectionBlock( 257 + block_id=Block.RECEIVER, 258 + text=MarkdownTextObject(text="¿A quien quieres felicitar?"), 259 + accessory=UserSelectElement( 260 + placeholder=PlainTextObject(text="Selecciona un usuario"), 261 + action_id=Actions.USER_SELECT, 262 + ), 263 + ), 264 + InputBlock( 265 + block_id=Block.MESSAGE, 266 + element=PlainTextInputElement( 267 + multiline=True, action_id=Actions.PLAIN_TEXT_INPUT 268 + ), 269 + label="Déjale un mensaje", 270 + ), 271 + ContextBlock( 272 + elements=[ 273 + PlainTextObject(text=f"Valor: {settings.CONGRATS_PRICE} kefis") 274 + ] 275 + ), 276 + ], 277 + ) 278 + 279 + 280 + class GiveHighFiveView(View): 281 + def __init__(self, *args, **kwargs): 282 + super().__init__( 283 + callback_id=ViewType.GIVE_HIGH_FIVE, 284 + type="modal", 285 + title=PlainTextObject(text="¡High Five!"), 286 + close=PlainTextObject(text="Cancelar"), 287 + submit=PlainTextObject(text="Chocar los cinco"), 288 + blocks=[ 289 + SectionBlock( 290 + block_id=Block.RECEIVER, 291 + text=MarkdownTextObject(text="¿A quien quieres felicitar?"), 292 + accessory=UserSelectElement( 293 + placeholder=PlainTextObject(text="Selecciona un usuario"), 294 + action_id=Actions.USER_SELECT, 295 + ), 296 + ), 297 + InputBlock( 298 + block_id=Block.MESSAGE, 299 + element=PlainTextInputElement( 300 + multiline=True, action_id=Actions.PLAIN_TEXT_INPUT 301 + ), 302 + label="Déjale un mensaje", 303 + ), 304 + ContextBlock( 305 + elements=[ 306 + PlainTextObject(text=f"Valor: {settings.HIGH_FIVE_PRICE} kefis") 307 + ] 308 + ), 309 + ], 310 + )
+244
kefi/tests/mocks.py
··· 1 + INTERACTIVITY_VIEW_ACTION_KUDOS = { 2 + "type": "view_submission", 3 + "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 4 + "user": { 5 + "id": "user_1", 6 + "username": "javichaques", 7 + "name": "javichaques", 8 + "team_id": "T0RS507QX", 9 + }, 10 + "api_app_id": "A0499FQH7V3", 11 + "token": "7n993LRqLRydOnIkEhymVcYD", 12 + "trigger_id": "4327568797879.25889007847.0f6a80604ac4fac92933e384a7792030", 13 + "view": { 14 + "id": "V04ARPPKCU8", 15 + "team_id": "T0RS507QX", 16 + "type": "modal", 17 + "blocks": [ 18 + { 19 + "type": "section", 20 + "block_id": "block_receiver", 21 + "text": { 22 + "type": "mrkdwn", 23 + "text": "\u00bfA quien quieres felicitar?", 24 + "verbatim": False, 25 + }, 26 + "accessory": { 27 + "type": "users_select", 28 + "action_id": "action_user_receiver", 29 + "placeholder": { 30 + "type": "plain_text", 31 + "text": "Selecciona un usuario", 32 + "emoji": True, 33 + }, 34 + }, 35 + }, 36 + { 37 + "type": "input", 38 + "block_id": "block_message", 39 + "label": { 40 + "type": "plain_text", 41 + "text": "D\u00e9jale un mensaje", 42 + "emoji": True, 43 + }, 44 + "optional": False, 45 + "dispatch_action": False, 46 + "element": { 47 + "type": "plain_text_input", 48 + "action_id": "action_message", 49 + "multiline": True, 50 + "dispatch_action_config": { 51 + "trigger_actions_on": ["on_enter_pressed"] 52 + }, 53 + }, 54 + }, 55 + { 56 + "type": "context", 57 + "block_id": "pYpG", 58 + "elements": [ 59 + {"type": "plain_text", "text": "Valor: 100 kefis", "emoji": True} 60 + ], 61 + }, 62 + ], 63 + "private_metadata": "", 64 + "callback_id": "give_kudos", 65 + "state": { 66 + "values": { 67 + "block_receiver": { 68 + "action_user_receiver": { 69 + "type": "users_select", 70 + "selected_user": "user_2", 71 + } 72 + }, 73 + "block_message": { 74 + "action_message": {"type": "plain_text_input", "value": "Test"} 75 + }, 76 + } 77 + }, 78 + "hash": "1667939174.QSF9AysY", 79 + "title": {"type": "plain_text", "text": "\u00a1Gracias!", "emoji": True}, 80 + "clear_on_close": False, 81 + "notify_on_close": False, 82 + "close": {"type": "plain_text", "text": "Cancelar", "emoji": True}, 83 + "submit": {"type": "plain_text", "text": "Enviar kefis", "emoji": True}, 84 + "previous_view_id": None, 85 + "root_view_id": "V04ARPPKCU8", 86 + "app_id": "A0499FQH7V3", 87 + "external_id": "", 88 + "app_installed_team_id": "T0RS507QX", 89 + "bot_id": "B0495T0Q0S2", 90 + }, 91 + "response_urls": [], 92 + "is_enterprise_install": False, 93 + "enterprise": None, 94 + } 95 + 96 + INTERACTIVITY_VIEW_ACTION_LEAVE = { 97 + "type": "view_submission", 98 + "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 99 + "user": { 100 + "id": "UCBKX2GQ4", 101 + "username": "marcos", 102 + "name": "marcos", 103 + "team_id": "T0RS507QX", 104 + }, 105 + "api_app_id": "A0499FQH7V3", 106 + "token": "7n993LRqLRydOnIkEhymVcYD", 107 + "trigger_id": "4338017757538.25889007847.877c730e79e8a4b1917de09d38a8daa0", 108 + "view": { 109 + "id": "V04AMLW580G", 110 + "team_id": "T0RS507QX", 111 + "type": "modal", 112 + "blocks": [ 113 + { 114 + "type": "section", 115 + "block_id": "9n+9", 116 + "text": { 117 + "type": "mrkdwn", 118 + "text": "*:wave: \\u00a1Hola! \\u00bfTe vienes a la Kefi Plaza?*", 119 + "verbatim": False, 120 + }, 121 + }, 122 + {"type": "divider", "block_id": "MSUNF"}, 123 + { 124 + "type": "section", 125 + "block_id": "IbY", 126 + "text": { 127 + "type": "mrkdwn", 128 + "text": "*Pr\\u00f3ximo encuentro*\\nFriday, 11 November\\n10:00\\n", 129 + "verbatim": False, 130 + }, 131 + "accessory": { 132 + "type": "image", 133 + "image_url": "https:\\/\\/api.slack.com\\/img\\/blocks\\/bkb_template_images\\/notifications.png", 134 + "alt_text": "calendar thumbnail", 135 + }, 136 + }, 137 + ], 138 + "private_metadata": "", 139 + "callback_id": "meet_leave", 140 + "state": {"values": {}}, 141 + "hash": "1667896295.Ry45BkuV", 142 + "title": {"type": "plain_text", "text": "Kefi", "emoji": True}, 143 + "clear_on_close": False, 144 + "notify_on_close": False, 145 + "close": {"type": "plain_text", "text": "Cancelar", "emoji": True}, 146 + "submit": { 147 + "type": "plain_text", 148 + "text": "\\u00a1Me apunto!", 149 + "emoji": True, 150 + }, 151 + "previous_view_id": None, 152 + "root_view_id": "V04AMLW580G", 153 + "app_id": "A0499FQH7V3", 154 + "external_id": "", 155 + "app_installed_team_id": "T0RS507QX", 156 + "bot_id": "B0495T0Q0S2", 157 + }, 158 + "response_urls": [], 159 + "is_enterprise_install": False, 160 + "enterprise": None, 161 + } 162 + 163 + INTERACTIVITY_VIEW_ACTION_JOIN = { 164 + "type": "view_submission", 165 + "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 166 + "user": { 167 + "id": "UCBKX2GQ4", 168 + "username": "marcos", 169 + "name": "marcos", 170 + "team_id": "T0RS507QX", 171 + }, 172 + "api_app_id": "A0499FQH7V3", 173 + "token": "7n993LRqLRydOnIkEhymVcYD", 174 + "trigger_id": "4338017757538.25889007847.877c730e79e8a4b1917de09d38a8daa0", 175 + "view": { 176 + "id": "V04AMLW580G", 177 + "team_id": "T0RS507QX", 178 + "type": "modal", 179 + "blocks": [ 180 + { 181 + "type": "section", 182 + "block_id": "9n+9", 183 + "text": { 184 + "type": "mrkdwn", 185 + "text": "*:wave: \\u00a1Hola! \\u00bfTe vienes a la Kefi Plaza?*", 186 + "verbatim": False, 187 + }, 188 + }, 189 + {"type": "divider", "block_id": "MSUNF"}, 190 + { 191 + "type": "section", 192 + "block_id": "IbY", 193 + "text": { 194 + "type": "mrkdwn", 195 + "text": "*Pr\\u00f3ximo encuentro*\\nFriday, 11 November\\n10:00\\n", 196 + "verbatim": False, 197 + }, 198 + "accessory": { 199 + "type": "image", 200 + "image_url": "https:\\/\\/api.slack.com\\/img\\/blocks\\/bkb_template_images\\/notifications.png", 201 + "alt_text": "calendar thumbnail", 202 + }, 203 + }, 204 + ], 205 + "private_metadata": "", 206 + "callback_id": "meet_join", 207 + "state": {"values": {}}, 208 + "hash": "1667896295.Ry45BkuV", 209 + "title": {"type": "plain_text", "text": "Kefi", "emoji": True}, 210 + "clear_on_close": False, 211 + "notify_on_close": False, 212 + "close": {"type": "plain_text", "text": "Cancelar", "emoji": True}, 213 + "submit": { 214 + "type": "plain_text", 215 + "text": "\\u00a1Me apunto!", 216 + "emoji": True, 217 + }, 218 + "previous_view_id": None, 219 + "root_view_id": "V04AMLW580G", 220 + "app_id": "A0499FQH7V3", 221 + "external_id": "", 222 + "app_installed_team_id": "T0RS507QX", 223 + "bot_id": "B0495T0Q0S2", 224 + }, 225 + "response_urls": [], 226 + "is_enterprise_install": False, 227 + "enterprise": None, 228 + } 229 + 230 + INTERACTIVITY_SHORTCUT = { 231 + "type": "shortcut", 232 + "token": "7n993LRqLRydOnIkEhymVcYD", 233 + "action_ts": "1667577118.758775", 234 + "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 235 + "user": { 236 + "id": "UCBKX2GQ4", 237 + "username": "marcos", 238 + "team_id": "T0RS507QX", 239 + }, 240 + "is_enterprise_install": False, 241 + "enterprise": None, 242 + "callback_id": "kefi_meets", 243 + "trigger_id": "4322087874981.25889007847.4301795f6ef2096cbc96f70c5d7bd48b", 244 + }
+35 -155
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.config import settings 9 + from kefi.models.core.helpers import received_balance, reset_wallets 10 + from kefi.models.database import User 11 + from kefi.tests.mocks import ( 12 + INTERACTIVITY_SHORTCUT, 13 + INTERACTIVITY_VIEW_ACTION_JOIN, 14 + INTERACTIVITY_VIEW_ACTION_KUDOS, 15 + INTERACTIVITY_VIEW_ACTION_LEAVE, 16 + ) 6 17 7 18 8 19 def test_default_interactivity(client: TestClient, mocker: MockerFixture): ··· 11 22 views_open.side_effect = [views_open_response] 12 23 response = client.post( 13 24 "/interactivity/", 14 - data={ 15 - "payload": json.dumps( 16 - { 17 - "type": "shortcut", 18 - "token": "7n993LRqLRydOnIkEhymVcYD", 19 - "action_ts": "1667577118.758775", 20 - "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 21 - "user": { 22 - "id": "UCBKX2GQ4", 23 - "username": "marcos", 24 - "team_id": "T0RS507QX", 25 - }, 26 - "is_enterprise_install": False, 27 - "enterprise": None, 28 - "callback_id": "kefi_meets", 29 - "trigger_id": "4322087874981.25889007847.4301795f6ef2096cbc96f70c5d7bd48b", 30 - } 31 - ) 32 - }, 25 + data={"payload": json.dumps(INTERACTIVITY_SHORTCUT)}, 33 26 ) 34 27 assert response.status_code == 200 35 28 ··· 41 34 post_message_user = mocker.patch.object(WebClient, "chat_postMessage") 42 35 post_message_user_response: dict = {} # Adds expected response here 43 36 post_message_user.side_effect = [post_message_user_response] 44 - payload = json.dumps( 45 - { 46 - "type": "view_submission", 47 - "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 48 - "user": { 49 - "id": "UCBKX2GQ4", 50 - "username": "marcos", 51 - "name": "marcos", 52 - "team_id": "T0RS507QX", 53 - }, 54 - "api_app_id": "A0499FQH7V3", 55 - "token": "7n993LRqLRydOnIkEhymVcYD", 56 - "trigger_id": "4338017757538.25889007847.877c730e79e8a4b1917de09d38a8daa0", 57 - "view": { 58 - "id": "V04AMLW580G", 59 - "team_id": "T0RS507QX", 60 - "type": "modal", 61 - "blocks": [ 62 - { 63 - "type": "section", 64 - "block_id": "9n+9", 65 - "text": { 66 - "type": "mrkdwn", 67 - "text": "*:wave: \\u00a1Hola! \\u00bfTe vienes a la Kefi Plaza?*", 68 - "verbatim": False, 69 - }, 70 - }, 71 - {"type": "divider", "block_id": "MSUNF"}, 72 - { 73 - "type": "section", 74 - "block_id": "IbY", 75 - "text": { 76 - "type": "mrkdwn", 77 - "text": "*Pr\\u00f3ximo encuentro*\\nFriday, 11 November\\n10:00\\n", 78 - "verbatim": False, 79 - }, 80 - "accessory": { 81 - "type": "image", 82 - "image_url": "https:\\/\\/api.slack.com\\/img\\/blocks\\/bkb_template_images\\/notifications.png", 83 - "alt_text": "calendar thumbnail", 84 - }, 85 - }, 86 - ], 87 - "private_metadata": "", 88 - "callback_id": "meet_join", 89 - "state": {"values": {}}, 90 - "hash": "1667896295.Ry45BkuV", 91 - "title": {"type": "plain_text", "text": "Kefi", "emoji": True}, 92 - "clear_on_close": False, 93 - "notify_on_close": False, 94 - "close": {"type": "plain_text", "text": "Cancelar", "emoji": True}, 95 - "submit": { 96 - "type": "plain_text", 97 - "text": "\\u00a1Me apunto!", 98 - "emoji": True, 99 - }, 100 - "previous_view_id": None, 101 - "root_view_id": "V04AMLW580G", 102 - "app_id": "A0499FQH7V3", 103 - "external_id": "", 104 - "app_installed_team_id": "T0RS507QX", 105 - "bot_id": "B0495T0Q0S2", 106 - }, 107 - "response_urls": [], 108 - "is_enterprise_install": False, 109 - "enterprise": None, 110 - } 111 - ) 37 + payload = json.dumps(INTERACTIVITY_VIEW_ACTION_JOIN) 112 38 response = client.post("/interactivity/", data={"payload": payload}) 113 39 assert response.status_code == 200 114 40 ··· 120 46 post_message_user = mocker.patch.object(WebClient, "chat_postMessage") 121 47 post_message_user_response: dict = {} # Adds expected response here 122 48 post_message_user.side_effect = [post_message_user_response] 123 - payload = json.dumps( 124 - { 125 - "type": "view_submission", 126 - "team": {"id": "T0RS507QX", "domain": "dekalabs"}, 127 - "user": { 128 - "id": "UCBKX2GQ4", 129 - "username": "marcos", 130 - "name": "marcos", 131 - "team_id": "T0RS507QX", 132 - }, 133 - "api_app_id": "A0499FQH7V3", 134 - "token": "7n993LRqLRydOnIkEhymVcYD", 135 - "trigger_id": "4338017757538.25889007847.877c730e79e8a4b1917de09d38a8daa0", 136 - "view": { 137 - "id": "V04AMLW580G", 138 - "team_id": "T0RS507QX", 139 - "type": "modal", 140 - "blocks": [ 141 - { 142 - "type": "section", 143 - "block_id": "9n+9", 144 - "text": { 145 - "type": "mrkdwn", 146 - "text": "*:wave: \\u00a1Hola! \\u00bfTe vienes a la Kefi Plaza?*", 147 - "verbatim": False, 148 - }, 149 - }, 150 - {"type": "divider", "block_id": "MSUNF"}, 151 - { 152 - "type": "section", 153 - "block_id": "IbY", 154 - "text": { 155 - "type": "mrkdwn", 156 - "text": "*Pr\\u00f3ximo encuentro*\\nFriday, 11 November\\n10:00\\n", 157 - "verbatim": False, 158 - }, 159 - "accessory": { 160 - "type": "image", 161 - "image_url": "https:\\/\\/api.slack.com\\/img\\/blocks\\/bkb_template_images\\/notifications.png", 162 - "alt_text": "calendar thumbnail", 163 - }, 164 - }, 165 - ], 166 - "private_metadata": "", 167 - "callback_id": "meet_leave", 168 - "state": {"values": {}}, 169 - "hash": "1667896295.Ry45BkuV", 170 - "title": {"type": "plain_text", "text": "Kefi", "emoji": True}, 171 - "clear_on_close": False, 172 - "notify_on_close": False, 173 - "close": {"type": "plain_text", "text": "Cancelar", "emoji": True}, 174 - "submit": { 175 - "type": "plain_text", 176 - "text": "\\u00a1Me apunto!", 177 - "emoji": True, 178 - }, 179 - "previous_view_id": None, 180 - "root_view_id": "V04AMLW580G", 181 - "app_id": "A0499FQH7V3", 182 - "external_id": "", 183 - "app_installed_team_id": "T0RS507QX", 184 - "bot_id": "B0495T0Q0S2", 185 - }, 186 - "response_urls": [], 187 - "is_enterprise_install": False, 188 - "enterprise": None, 189 - } 190 - ) 49 + payload = json.dumps(INTERACTIVITY_VIEW_ACTION_LEAVE) 50 + response = client.post("/interactivity/", data={"payload": payload}) 51 + assert response.status_code == 200 52 + 53 + 54 + def test_view_action_kudos(client: TestClient, mocker: MockerFixture, session: Session): 55 + users = [User(slack_user_id="user_1"), User(slack_user_id="user_2")] 56 + for user in users: 57 + session.add(user) 58 + reset_wallets(session=session) 59 + views_open = mocker.patch.object(WebClient, "views_open") 60 + views_open_response: dict = {} # Adds expected response here 61 + views_open.side_effect = [views_open_response] 62 + post_message_user = mocker.patch.object(WebClient, "chat_postMessage") 63 + post_message_user_response: dict = {} # Adds expected response here 64 + post_message_user.side_effect = [ 65 + post_message_user_response, 66 + post_message_user_response, 67 + ] 68 + payload = json.dumps(INTERACTIVITY_VIEW_ACTION_KUDOS) 191 69 response = client.post("/interactivity/", data={"payload": payload}) 192 70 assert response.status_code == 200 71 + receiver_balance = received_balance(user=users[1], session=session) 72 + assert receiver_balance == settings.KUDOS_PRICE