Skip to content

Commit 0b6ed95

Browse files
committed
Add /mentorVolunteer slash command handler
1 parent ac5bed4 commit 0b6ed95

6 files changed

Lines changed: 294 additions & 10 deletions

File tree

pybot/endpoints/slack/actions/__init__.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
set_requested_mentor,
1515
set_requested_service,
1616
)
17+
from .mentor_volunteer import (
18+
add_volunteer_skillset,
19+
clear_volunteer_skillsets,
20+
submit_mentor_volunteer,
21+
)
1722
from .new_member import (
1823
member_greeted,
1924
open_suggestion,
@@ -67,7 +72,17 @@ def create_endpoints(plugin: SlackPlugin):
6772
"affiliation", set_group, action_id="affiliation_select", wait=False
6873
)
6974
plugin.on_block(
70-
"submission", mentor_request_submit, action_id="submit_btn", wait=False
75+
"submission", mentor_request_submit, action_id="submit_mentor_btn", wait=False
76+
)
77+
78+
# mentor volunteer actions
79+
plugin.on_block("volunteer_skillset", add_volunteer_skillset, wait=False)
80+
plugin.on_block("clear_volunteer_skillsets", clear_volunteer_skillsets, wait=False)
81+
plugin.on_block(
82+
"submission",
83+
submit_mentor_volunteer,
84+
action_id="submit_mentor_volunteer_btn",
85+
wait=False,
7186
)
7287

7388
# mentorship claims
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
from enum import IntEnum
2+
3+
from sirbot import SirBot
4+
from slack import methods
5+
from slack.actions import Action
6+
7+
from pybot.endpoints.slack.message_templates.mentor_volunteer import MentorVolunteer
8+
from pybot.endpoints.slack.utils import MENTOR_CHANNEL
9+
10+
11+
class VolunteerBlockIndex(IntEnum):
12+
SKILLSET_OPTIONS = 2
13+
SELECTED_SKILLSETS = 3
14+
SUBMIT = 5
15+
16+
17+
async def add_volunteer_skillset(action: Action, app: SirBot) -> None:
18+
slack = app.plugins["slack"].api
19+
20+
request = MentorVolunteer(action)
21+
22+
selected_skill = request.selected_option
23+
request.add_skillset(selected_skill["value"])
24+
await request.update_message(slack)
25+
26+
27+
async def clear_volunteer_skillsets(action: Action, app: SirBot) -> None:
28+
slack = app.plugins["slack"].api
29+
30+
request = MentorVolunteer(action)
31+
32+
request.clear_skillsets()
33+
await request.update_message(slack)
34+
35+
36+
async def submit_mentor_volunteer(action: Action, app: SirBot) -> None:
37+
slack = app.plugins["slack"].api
38+
admin_slack = app.plugins["admin_slack"].api
39+
airtable = app.plugins["airtable"].api
40+
bot_user_id = action["user"]["id"]
41+
42+
request = MentorVolunteer(action)
43+
44+
if not request.validate_self():
45+
request.add_errors()
46+
await request.update_message(slack)
47+
return
48+
49+
user_id = action["user"]["id"]
50+
user_info = await slack.query(methods.USERS_INFO, {"user": user_id})
51+
airtable_fields = await build_airtable_fields(action, request, user_info)
52+
53+
airtable_response = await airtable.add_record(
54+
"Mentors", {"fields": airtable_fields}
55+
)
56+
57+
if "error" in airtable_response:
58+
request.airtable_error(airtable_response)
59+
else:
60+
61+
# checks if the user submitting the form owns the admin token used for the bot
62+
# stops an exception being being raised if a user tries to invite themselves to a channel
63+
if bot_user_id != user_id:
64+
await admin_slack.query(
65+
methods.CONVERSATIONS_INVITE,
66+
{"channel": MENTOR_CHANNEL, "users": [user_id]},
67+
)
68+
request.on_submit_success()
69+
70+
await request.update_message(slack)
71+
72+
73+
async def build_airtable_fields(action, request, user_info):
74+
username = action["user"]["name"]
75+
email = user_info["user"]["profile"]["email"]
76+
name = user_info["user"]["real_name"]
77+
airtable_fields = {
78+
"Slack Name": username,
79+
"Full Name": name,
80+
"Skillsets": request.skillsets,
81+
"Email": email,
82+
}
83+
return airtable_fields

pybot/endpoints/slack/commands.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
from slack.commands import Command
88

99
from pybot.endpoints.slack.message_templates.commands import (
10-
mentor_reques_blocks,
10+
mentor_request_blocks,
11+
mentor_volunteer_blocks,
1112
ticket_dialog,
1213
)
1314
from pybot.endpoints.slack.utils import MODERATOR_CHANNEL
@@ -26,6 +27,7 @@ def create_endpoints(plugin: SlackPlugin):
2627
plugin.on_command("/ticket", slash_ticket, wait=False)
2728
plugin.on_command("/roll", slash_roll, wait=False)
2829
plugin.on_command("/mentor", slash_mentor, wait=False)
30+
plugin.on_command("/mentorvolunteer", slash_mentor_volunteer, wait=False)
2931

3032

3133
@catch_command_slack_error
@@ -35,7 +37,7 @@ async def slash_mentor(command: Command, app: SirBot):
3537
mentors = await airtable.get_all_records("Mentors", "Full Name")
3638
skillsets = await airtable.get_all_records("Skillsets", "Name")
3739

38-
blocks = mentor_reques_blocks(services, mentors, skillsets)
40+
blocks = mentor_request_blocks(services, mentors, skillsets)
3941

4042
response = {
4143
"text": "Mentor Request Form",
@@ -46,6 +48,22 @@ async def slash_mentor(command: Command, app: SirBot):
4648
await app.plugins["slack"].api.query(methods.CHAT_POST_MESSAGE, response)
4749

4850

51+
@catch_command_slack_error
52+
async def slash_mentor_volunteer(command: Command, app: SirBot) -> None:
53+
airtable = app.plugins["airtable"].api
54+
skillsets = await airtable.get_all_records("Skillsets", "Name")
55+
56+
blocks = mentor_volunteer_blocks(skillsets)
57+
response = {
58+
"text": "Mentor Sign up Form",
59+
"blocks": blocks,
60+
"channel": command["user_id"],
61+
"as_user": True,
62+
}
63+
64+
await app.plugins["slack"].api.query(methods.CHAT_POST_MESSAGE, response)
65+
66+
4967
@catch_command_slack_error
5068
async def slash_ticket(command: Command, app: SirBot):
5169
trigger_id = command["trigger_id"]

pybot/endpoints/slack/message_templates/commands.py

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from typing import List
2+
3+
14
def ticket_dialog(clicker_email, text):
25
return {
36
"callback_id": "open_ticket",
@@ -17,7 +20,7 @@ def ticket_dialog(clicker_email, text):
1720
}
1821

1922

20-
def mentor_reques_blocks(services, mentors, skillsets):
23+
def mentor_request_blocks(services, mentors, skillsets):
2124
return [
2225
{
2326
"type": "section",
@@ -132,7 +135,75 @@ def mentor_reques_blocks(services, mentors, skillsets):
132135
"elements": [
133136
{
134137
"type": "button",
135-
"action_id": "submit_btn",
138+
"action_id": "submit_mentor_btn",
139+
"text": {"type": "plain_text", "text": "Submit"},
140+
"style": "primary",
141+
"value": "submit",
142+
},
143+
{
144+
"type": "button",
145+
"action_id": "cancel_btn",
146+
"text": {"type": "plain_text", "text": "Cancel"},
147+
"style": "danger",
148+
"value": "cancel",
149+
},
150+
],
151+
},
152+
]
153+
154+
155+
def mentor_volunteer_blocks(skillsets: List[str]) -> List[dict]:
156+
return [
157+
{
158+
"type": "section",
159+
"text": {
160+
"type": "mrkdwn",
161+
"text": (
162+
"Thank you for volunteering to be a mentor for the Operation Code community! If you're looking "
163+
"for the mentor request form, please use `/mentor` instead."
164+
),
165+
},
166+
},
167+
{"type": "divider"},
168+
{
169+
"type": "section",
170+
"block_id": "volunteer_skillset",
171+
"text": {
172+
"type": "mrkdwn",
173+
"text": "*What area(s) are you interested in mentoring in?*",
174+
},
175+
"accessory": {
176+
"type": "static_select",
177+
"action_id": "skillset_select",
178+
"placeholder": {"type": "plain_text", "text": "Skillset"},
179+
"options": [
180+
{
181+
"text": {"type": "plain_text", "text": skillset},
182+
"value": skillset,
183+
}
184+
for skillset in skillsets
185+
],
186+
},
187+
},
188+
{
189+
"type": "section",
190+
"block_id": "clear_volunteer_skillsets",
191+
"text": {"type": "mrkdwn", "text": "*Selected Skillsets*"},
192+
"accessory": {
193+
"type": "button",
194+
"action_id": "clear_skillsets_btn",
195+
"text": {"type": "plain_text", "text": "Reset Skillsets"},
196+
"value": "reset_skillsets",
197+
},
198+
},
199+
{"type": "divider"},
200+
{
201+
"type": "actions",
202+
"block_id": "submission",
203+
"elements": [
204+
{
205+
"type": "button",
206+
"action_id": "submit_mentor_volunteer_btn",
136207
"text": {"type": "plain_text", "text": "Submit"},
137208
"style": "primary",
138209
"value": "submit",
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from enum import IntEnum
2+
from typing import MutableMapping
3+
4+
from pybot.endpoints.slack.utils import MENTOR_CHANNEL
5+
6+
from .block_action import BlockAction
7+
8+
9+
class VolunteerBlockIndex(IntEnum):
10+
SKILLSET_OPTIONS = 2
11+
SELECTED_SKILLSETS = 3
12+
SUBMIT = 5
13+
14+
15+
class MentorVolunteer(BlockAction):
16+
def __init__(self, raw_action: MutableMapping):
17+
super().__init__(raw_action)
18+
19+
if "original_message" not in self:
20+
self["original_message"] = {}
21+
22+
@property
23+
def skillsets(self) -> [str]:
24+
if self.skillset_fields:
25+
return [field["text"] for field in self.skillset_fields]
26+
return []
27+
28+
@property
29+
def skillset_fields(self) -> list:
30+
return self.blocks[VolunteerBlockIndex.SELECTED_SKILLSETS].get("fields", [])
31+
32+
def add_skillset(self, skillset: str) -> None:
33+
"""
34+
Appends the new skillset to the displayed skillsets
35+
"""
36+
if skillset not in self.skillsets:
37+
new_field = {"type": "plain_text", "text": skillset, "emoji": True}
38+
self.blocks[VolunteerBlockIndex.SELECTED_SKILLSETS].setdefault(
39+
"fields", []
40+
).append(new_field)
41+
42+
def clear_skillsets(self) -> None:
43+
if self.skillset_fields:
44+
del self.blocks[VolunteerBlockIndex.SELECTED_SKILLSETS]["fields"]
45+
46+
def validate_self(self):
47+
if not self.skillsets:
48+
return False
49+
50+
self.clear_errors()
51+
return True
52+
53+
def add_errors(self) -> None:
54+
submit_attachment = {
55+
"text": ":warning: Please select at least one area. :warning:",
56+
"color": "danger",
57+
}
58+
self.attachments = [submit_attachment]
59+
60+
def airtable_error(self, airtable_response) -> None:
61+
error_attachment = {
62+
"text": (
63+
f"Something went wrong.\n"
64+
f'Error Type:{airtable_response["error"]["type"]}\n'
65+
f'Error Message: {airtable_response["error"]["message"]}'
66+
),
67+
"color": "danger",
68+
}
69+
self.attachments = [error_attachment]
70+
71+
def on_submit_success(self):
72+
done_blocks = [
73+
{"type": "section", "text": {"type": "mrkdwn", "text": success_message}},
74+
{
75+
"type": "actions",
76+
"block_id": "submission",
77+
"elements": [
78+
{
79+
"type": "button",
80+
"action_id": "cancel_btn",
81+
"text": {"type": "plain_text", "text": "Dismiss"},
82+
"value": "dismiss",
83+
}
84+
],
85+
},
86+
]
87+
self.blocks = done_blocks
88+
89+
90+
success_message = (
91+
"Thank you for signing up to be a mentor for Operation Code! You should have been automatically "
92+
f"added to the <#{MENTOR_CHANNEL}|mentors-internal> channel. There is a bot that posts in that "
93+
"channel when someone signs up for a 30 minute session with a mentor. If the skillsets they request "
94+
"match the ones you listed when you signed up, you'll be notified in the thread. Click the green "
95+
"button to claim them and reach out via DM to schedule a slack call. There are also a few pinned "
96+
f"items in that channel that may be helpful. If you have any questions, please DM <@aaron-s|aaron-s>.\n\n"
97+
"We don't currently have a formal long term mentorship program, but if you feel like continuing to "
98+
"keep in contact\n\n"
99+
"with any members you speak to, that's perfectly fine. "
100+
"Thank you for signing up!"
101+
)

pybot/endpoints/slack/utils/__init__.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
import os
22

3-
from dotenv import load_dotenv
4-
5-
load_dotenv()
6-
73
MENTOR_CHANNEL = os.environ.get("MENTOR_CHANNEL") or "G1DRT62UC"
84
COMMUNITY_CHANNEL = os.environ.get("COMMUNITY_CHANNEL") or "G12343"
95
MODERATOR_CHANNEL = os.environ.get("MODERATOR_CHANNEL") or "G8NDRJJF9"
@@ -23,5 +19,5 @@
2319
"signing_secret": os.environ.get("SLACK_BOT_SIGNING_SECRET"),
2420
"verify": os.environ.get("VERIFICATION_TOKEN"),
2521
"bot_id": os.environ.get("SLACK_BOT_ID"),
26-
"bot_user_id": os.environ.get("SLACK_BOT_ID"),
22+
"bot_user_id": os.environ.get("SLACK_BOT_USER_ID"),
2723
}

0 commit comments

Comments
 (0)