Skip to content

Commit

Permalink
Add ability to select which users you want to include for the retro (#…
Browse files Browse the repository at this point in the history
…592)

* Allow users to select who to include in the retro

* Adding users ability to select users to send calendar invites

* Removing comments that were not needed

* Adding unit tests and extracting the function to get the users in it's separate function

* Removing comments that are no longer needed

* Changing the text of the dialog box to indicate the change in functionality

* Moving the fetch_user_details function to the integrations/sslack/channels directory

* Removing unecesasry comments and formatting
  • Loading branch information
sylviamclaughlin authored Jul 30, 2024
1 parent 805efd6 commit 9843116
Show file tree
Hide file tree
Showing 8 changed files with 207 additions and 126 deletions.
1 change: 1 addition & 0 deletions app/integrations/google_workspace/google_calendar.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def insert_event(start, end, emails, title, incident_document, **kwargs):
body=body,
calendarId="primary",
supportsAttachments=True,
sendUpdates="all",
conferenceDataVersion=1,
)
# Handle the instance differently if the result is a dictionary or a tuple and get the calendar link and start time
Expand Down
34 changes: 34 additions & 0 deletions app/integrations/slack/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,37 @@ def get_messages_in_time_period(client, channel_id, time_delta):
) # Return only messages from users
else:
return []


def fetch_user_details(client, channel_id):
"""
Fetches user details from a Slack channel, excluding users with the real names 'SRE' and 'SRE Dev'.
Parameters:
client (object): The Slack client used to interact with the Slack API.
channel_id (str): The ID of the Slack channel from which to fetch user details.
Returns:
list: A list of dictionaries containing user details, formatted for Slack modal blocks.
"""
# get all members of the channel
result = client.conversations_members(channel=channel_id)
users = []
# extract the real name of the user and append it to the users list, excluding users with the real names 'SRE' and 'SRE Dev'
for user_id in result["members"]:
user_info = client.users_info(user=user_id)
if (
user_info["user"]["real_name"] != "SRE"
and user_info["user"]["real_name"] != "SRE Dev"
):
users.append(
{
"text": {
"type": "plain_text",
"text": user_info["user"]["real_name"],
"emoji": True,
},
"value": user_info["user"]["id"],
}
)
return users
49 changes: 29 additions & 20 deletions app/modules/incident/incident_helper.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import os
import json
import logging
from integrations import google_drive
Expand Down Expand Up @@ -451,11 +450,6 @@ def schedule_incident_retro(client, body, ack):
)
return

# Get security group members. We want to exclude them from the list of people to schedule the event for
security_group_users = client.usergroups_users_list(
usergroup=os.getenv("SLACK_SECURITY_USER_GROUP_ID")
)["users"]

# get all users in a channel
users = client.conversations_members(channel=channel_id)["members"]

Expand All @@ -464,16 +458,6 @@ def schedule_incident_retro(client, body, ack):
channel_name = "Incident Retro"
logging.warning("Channel topic is empty. Setting it to 'Incident Retro'")

user_emails = []

# get the email addresses of all the users in the channel, except security group members and any apps/bots in the channel, since bots don't have an email address associated with them.
for user in users:
if user not in security_group_users:
response = client.users_info(user=user)["user"]["profile"]
# don't include bots in the list of users
if "bot_id" not in response:
user_emails.append(response["email"])

# get the incident document
# get and update the incident document
document_id = ""
Expand All @@ -493,13 +477,15 @@ def schedule_incident_retro(client, body, ack):
# convert the data to string so that we can send it as private metadata
data_to_send = json.dumps(
{
"emails": user_emails,
"name": channel_name,
"incident_document": document_id,
"channel_id": channel_id,
}
)

# Fetch user details from all members of the channel
users = slack_channels.fetch_user_details(client, channel_id)

blocks = {
"type": "modal",
"callback_id": "view_save_event",
Expand All @@ -523,6 +509,20 @@ def schedule_incident_retro(client, body, ack):
"text": "How many days from now should I start checking the calendar for availability?",
},
},
{
"type": "input",
"block_id": "user_select_block",
"label": {
"type": "plain_text",
"text": "Select everyone you want to include in the retro calendar invite",
"emoji": True,
},
"element": {
"type": "multi_static_select",
"action_id": "user_select_action",
"options": users,
},
},
{"type": "divider"},
{
"type": "section",
Expand All @@ -542,7 +542,7 @@ def schedule_incident_retro(client, body, ack):
"type": "section",
"text": {
"type": "mrkdwn",
"text": "2. A proposed event will be added to everyone's calendar that is part of this channel (except Security team).",
"text": "2. A proposed event will be added to everyone's calendar that is selected.",
},
},
{
Expand Down Expand Up @@ -577,9 +577,18 @@ def save_incident_retro(client, ack, body, view):
# get the number of days data from the view and convert to an integer
days = int(view["state"]["values"]["number_of_days"]["number_of_days"]["value"])

# pass the data using the view["private_metadata"] to the schedule_event function
result = schedule_retro.schedule_event(view["private_metadata"], days)
# get all the users selected in the multi select block
users = view["state"]["values"]["user_select_block"]["user_select_action"][
"selected_options"
]
user_emails = []
for user in users:
user_id = user["value"].strip()
user_email = client.users_info(user=user_id)["user"]["profile"]["email"]
user_emails.append(user_email)

# pass the data using the view["private_metadata"] to the schedule_event function
result = schedule_retro.schedule_event(view["private_metadata"], days, user_emails)
# if we could not schedule the event, display a message to the user that the event could not be scheduled
if result is None:
blocks = {
Expand Down
9 changes: 4 additions & 5 deletions app/modules/incident/schedule_retro.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@


# Schedule a calendar event by finding the first available slot in the next 60 days that all participants are free in and book the event
def schedule_event(event_details, days):
def schedule_event(event_details, days, user_emails):
# Define the time range for the query
now = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
# time_min is the current time + days and time_max is the current time + 60 days + days
Expand All @@ -20,11 +20,10 @@ def schedule_event(event_details, days):

# Construct the items array
items = []
emails = json.loads(event_details).get("emails")
incident_name = json.loads(event_details).get("name")
for email in emails:
for email in user_emails:
email = email.strip()
items.append({"id": email})
incident_name = json.loads(event_details).get("name")

# get the incident document link
incident_document = json.loads(event_details).get("incident_document")
Expand Down Expand Up @@ -63,7 +62,7 @@ def schedule_event(event_details, days):
result = insert_event(
first_available_start.isoformat(),
first_available_end.isoformat(),
emails,
user_emails,
"Retro " + incident_name,
incident_document,
**event_config,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ def test_insert_event_no_kwargs_no_delegated_email(
},
calendarId="primary",
supportsAttachments=True,
sendUpdates="all",
conferenceDataVersion=1,
)
assert not mock_convert_string_to_camel_case.called
Expand Down Expand Up @@ -297,6 +298,7 @@ def test_insert_event_with_kwargs(
},
calendarId="primary",
supportsAttachments=True,
sendUpdates="all",
conferenceDataVersion=1,
)
for key in kwargs:
Expand Down Expand Up @@ -372,6 +374,7 @@ def test_insert_event_with_no_document(
},
calendarId="primary",
supportsAttachments=True,
sendUpdates="all",
conferenceDataVersion=1,
)
for key in kwargs:
Expand Down Expand Up @@ -442,6 +445,7 @@ def test_insert_event_google_hangout_link_created(
},
calendarId="primary",
supportsAttachments=True,
sendUpdates="all",
conferenceDataVersion=1,
)
assert mock_unique_id.called
Expand Down
58 changes: 58 additions & 0 deletions app/tests/integrations/slack/test_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,61 @@ def test_get_messages_in_time_period_with_error():
channels.get_messages_in_time_period(client, "channel_id", timedelta(days=1))
== []
)


def test_fetch_user_details():
client_mock = MagicMock()
client_mock.conversations_members.return_value = {
"members": ["U123", "U456", "U789"]
}
client_mock.users_info.side_effect = [
{"user": {"real_name": "Alice", "id": "U123"}},
{"user": {"real_name": "SRE", "id": "U456"}},
{"user": {"real_name": "Bob", "id": "U789"}},
]

expected_users = [
{
"text": {
"type": "plain_text",
"text": "Alice",
"emoji": True,
},
"value": "U123",
},
{
"text": {
"type": "plain_text",
"text": "Bob",
"emoji": True,
},
"value": "U789",
},
]

result = channels.fetch_user_details(client_mock, "C123456")
assert result == expected_users


def test_fetch_user_details_no_users():
client_mock = MagicMock()
client_mock.conversations_members.return_value = {"members": []}

expected_users = []

result = channels.fetch_user_details(client_mock, "C123456")
assert result == expected_users


def test_fetch_user_details_all_sre():
client_mock = MagicMock()
client_mock.conversations_members.return_value = {"members": ["U123", "U456"]}
client_mock.users_info.side_effect = [
{"user": {"real_name": "SRE", "id": "U123"}},
{"user": {"real_name": "SRE Dev", "id": "U456"}},
]

expected_users = []

result = channels.fetch_user_details(client_mock, "C123456")
assert result == expected_users
Loading

0 comments on commit 9843116

Please sign in to comment.