Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions common/utils/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -300,20 +300,21 @@ def create_slack_channel(channel_name):
logger.debug("create_slack_channel end")


def send_slack(message="", channel="", icon_emoji=None, username="Hackathon Bot"):
def send_slack(message="", channel="", icon_emoji=None, username="Hackathon Bot", blocks=None):
client = get_client()
channel_id = get_channel_id_from_channel_name(channel)
logger.info(f"Got channel id {channel_id}")

if channel_id is None:
logger.warning("Unable to get channel id from name, might be a user?")
channel_id = channel

logger.info("Sending message...")
try:
kwargs = {
"channel": channel_id,
"blocks": [
"text": message,
"blocks": blocks if blocks else [
SectionBlock(
text={
"type": "mrkdwn",
Expand All @@ -335,19 +336,20 @@ def send_slack(message="", channel="", icon_emoji=None, username="Hackathon Bot"
assert e.response["error"]


def async_send_slack(message="", channel="", icon_emoji=None, username="Hackathon Bot"):
def async_send_slack(message="", channel="", icon_emoji=None, username="Hackathon Bot", blocks=None):
"""
Send a Slack message asynchronously using threading.
This allows the calling function to return immediately without waiting for the Slack API call.

:param message: The message to send
:param channel: The channel name or user ID to send to
:param channel: The channel name or user ID to send to
:param icon_emoji: Optional emoji icon
:param username: The username for the bot
:param blocks: Optional Block Kit blocks for rich formatting
"""
def _send_slack_thread():
try:
send_slack(message=message, channel=channel, icon_emoji=icon_emoji, username=username)
send_slack(message=message, channel=channel, icon_emoji=icon_emoji, username=username, blocks=blocks)
logger.info(f"Async Slack message sent successfully to {channel}")
except Exception as e:
logger.error(f"Error sending async Slack message to {channel}: {e}")
Expand Down
122 changes: 104 additions & 18 deletions services/volunteers_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,39 +334,125 @@ def send_admin_notification_email(volunteer_data: Dict[str, Any], is_update: boo

def send_slack_volunteer_notification(volunteer_data: Dict[str, Any], is_update: bool = False) -> bool:
"""
Send a notification to Slack when a volunteer form is submitted or updated.
Send a rich Block Kit notification to Slack when a volunteer form is submitted or updated.

Args:
volunteer_data: The volunteer data
is_update: Whether this is an update to an existing volunteer

Returns:
True if notification was sent successfully, False otherwise
"""
action_type = "updated" if is_update else "submitted"
import datetime as _dt

# --- Derive display values ---
first_name = volunteer_data.get('firstName', '')
last_name = volunteer_data.get('lastName', '')
name = volunteer_data.get('name', '')

display_name = name or f"{first_name} {last_name}".strip() or "Unknown"

email = volunteer_data.get('email', '')
volunteer_type = volunteer_data.get('volunteer_type', '')
volunteer_type = volunteer_data.get('volunteer_type', 'volunteer')
event_id = volunteer_data.get('event_id', '')

slack_message = f"""
New volunteer form {action_type}:
*Name:* {name} {first_name} {last_name}
*Email:* {email}
*Type:* {volunteer_type}
*Event ID:* {event_id}
"""


type_emoji = {
'mentor': ':brain:',
'judge': ':scales:',
'volunteer': ':raised_hands:',
'sponsor': ':star:',
'hacker': ':computer:',
}
emoji = type_emoji.get(volunteer_type, ':raised_hands:')
action_label = "Updated" if is_update else "New"
role_label = volunteer_type.capitalize() if volunteer_type else "Volunteer"

# --- Fallback plain-text (required by Slack for notifications/accessibility) ---
fallback_text = f"{action_label} {role_label} Application — {display_name} ({email})"

# --- Build Block Kit blocks ---
blocks = []

# Header
blocks.append({
"type": "header",
"text": {"type": "plain_text", "text": f"{emoji} {action_label} {role_label} Application", "emoji": True}
})

blocks.append({"type": "divider"})

# Core fields (two-column layout)
fields = [
{"type": "mrkdwn", "text": f"*Name:*\n{display_name}"},
{"type": "mrkdwn", "text": f"*Email:*\n{email}"},
{"type": "mrkdwn", "text": f"*Role:*\n{role_label}"},
{"type": "mrkdwn", "text": f"*Event:*\n{event_id or '—'}"},
]
blocks.append({"type": "section", "fields": fields})

# Additional details (only if present)
detail_lines = []
company = volunteer_data.get('company', '')
if company:
detail_lines.append(f"*Company:* {company}")

linkedin = volunteer_data.get('linkedinProfile', '')
if linkedin:
detail_lines.append(f"*LinkedIn:* <{linkedin}|Profile>")

expertise = volunteer_data.get('expertise', '')
if expertise:
detail_lines.append(f"*Expertise:* {expertise}")

sw_specifics = volunteer_data.get('softwareEngineeringSpecifics', '')
if sw_specifics:
detail_lines.append(f"*SW Engineering:* {sw_specifics}")

in_person = volunteer_data.get('inPerson')
if in_person is not None:
in_person_label = "Yes :office:" if in_person else "No (remote) :globe_with_meridians:"
detail_lines.append(f"*In-Person:* {in_person_label}")

availability = volunteer_data.get('availability', '')
if availability:
# Truncate long availability strings for readability
avail_display = availability if len(availability) <= 200 else availability[:200] + "…"
detail_lines.append(f"*Availability:* {avail_display}")

available_days = volunteer_data.get('availableDays', '')
if available_days:
detail_lines.append(f"*Available Days:* {available_days}")

skills = volunteer_data.get('skills', '')
if skills:
detail_lines.append(f"*Skills:* {skills}")

experience = volunteer_data.get('experience', '')
if experience:
detail_lines.append(f"*Experience:* {experience}")

if detail_lines:
blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": "\n".join(detail_lines)}})

# Context footer — timestamp + Slack workspace lookup result
context_elements = [
{"type": "mrkdwn", "text": f"Submitted {_dt.datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}"}
]
slack_user_id = volunteer_data.get('slack_user_id', '')
if slack_user_id:
context_elements.append({"type": "mrkdwn", "text": f":white_check_mark: Found in Slack workspace (<@{slack_user_id}>)"})
else:
context_elements.append({"type": "mrkdwn", "text": ":x: Not yet found in Slack workspace"})

blocks.append({"type": "context", "elements": context_elements})

# --- Send ---
try:
send_slack(
message=slack_message,
message=fallback_text,
channel="volunteer-applications",
icon_emoji=":raising_hand:",
username="Volunteer Bot"
icon_emoji=emoji,
username="Volunteer Bot",
blocks=blocks
)
info(logger, "Sent Slack notification about volunteer", email=email, is_update=is_update)
return True
Expand Down
Loading