Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add suggested channels greeting message #106

Closed
wants to merge 3 commits into from
Closed
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
7 changes: 7 additions & 0 deletions pybot/endpoints/slack/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
link_backend_user,
send_community_notification,
send_user_greetings,
get_profile_suggestions,
build_suggestion_messages,
)

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -42,3 +44,8 @@ async def team_join(event: Event, app: SirBot) -> None:
headers = await get_backend_auth_headers(app.http_session)
if headers:
await link_backend_user(user_id, headers, slack_api, app.http_session)

suggested_channels = get_profile_suggestions(slack_api)
if suggested_channels:
suggestion_messages = build_suggestion_messages(user_id, suggested_channels)
await asyncio.wait([send_user_greetings(suggestion_messages, slack_api)])
7 changes: 7 additions & 0 deletions pybot/endpoints/slack/utils/event_messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,10 @@ def base_resources():
],
},
]


def profile_suggestion_message(channels):
message = "We found some channels that you might be interested in based on your profile information.\n"
for channel, name in channels:
message += f"<#{channel}|{name}> "
return message
71 changes: 70 additions & 1 deletion pybot/endpoints/slack/utils/event_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
external_button_attachments,
second_team_join_message,
team_join_initial_message,
profile_suggestion_message,
)

logger = logging.getLogger(__name__)
Expand All @@ -32,7 +33,9 @@ def base_user_message(user_id: str) -> Message:

def build_messages(user_id) -> Tuple[Message, Message, Message, Message]:
initial_message = base_user_message(user_id)
initial_message["text"] = team_join_initial_message(user_id)
initial_message["text"] = team_join_initial_message(
user_id
) # <--- Initial message is built

second_message = base_user_message(user_id)
second_message["text"] = second_team_join_message()
Expand Down Expand Up @@ -103,3 +106,69 @@ async def get_backend_auth_headers(session: ClientSession) -> Dict[str, str]:
data = await response.json()
headers = {"Authorization": f"Bearer {data['token']}"}
return headers


async def get_profile_suggestions(
slack_id: int,
auth_header: Dict[str, str],
slack_api: SlackAPI,
session: ClientSession,
) -> List[Tuple[str, str]]:
"""
Generates a list of slack channels to suggest

:param slack_id: The slack_id to return profile information from
:return: A list of slack channel names to suggest for the user
"""
results = []

# Get profile data that we want to build suggestions from.
user_info = await slack_api.query(methods.USERS_INFO, {"user": slack_id})
email = user_info["user"]["profile"]["email"]

async with session.get(
f"{BACKEND_URL}/auth/profile/admin/",
headers=auth_header,
params={"email": email},
) as response:
data = await response.json()
logger.info(f"Retrieved profile data from email: {email}")

# Get the names of all public, non-archived channels
channels = await slack_api.query(
methods.CONVERSATION_LIST, {"exclude_archived": True}
)

# Get a list of all the profile data we want to compare
possible_suggestions = parse_suggestions_from_profile(data)

# For each value in the profile data, check that we have a channel name that matches.
for info in possible_suggestions:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not to worried about a double for loop but you could avoid the lookup if you converted the list of channels into a set, or a key value pair.

But I think the naming should be clearer:

for suggestion in possible_suggestions:
    for channel in channels:
        if suggestion in channel[1]:
            results.append((channel[0], channel[1]))
            break

for channel in channels:
if info in channel[1]:
Copy link
Member

@apex-omontgomery apex-omontgomery Sep 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how you got that the channel can be indexed...

"channels": [
        {
            "id": "C0G9QF9GW",
            "name": "random",
            "is_channel": true,
            "created": 1449709280,
            "creator": "U0G9QF9C6",
            "is_archived": false,
            "is_general": false,
            "name_normalized": "random",
            "is_shared": false,
            "is_org_shared": false,
            "is_member": true,
            "is_private": false,
             ........
        },

I think you need to compare the channel['name'] to the suggestion. But you should also exclude the channel when channel['is_private'] == True as we don't want to make the names of these channels visible.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh thanks for catching that. 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the above, ignore the is_private part but we still need to make sure this works.

results.append((channel[0], channel[1]))
break

return results


def parse_suggestions_from_profile(profile: Dict) -> List[str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not very clear at first what this is doing.

  1. You have a list of profile fields we care about
  2. You want to iterate through these profile fields.
  3. Using non-empty profile fields, grab each item.

For some profile fields they are lists, and some profile fields they are single items.

This is because we are storing lists of items as strings joined by commas.

Stylistically I think something like this is a bit clearer.

def filter_non_empty_profile_fields(profile: Dict) -> List[str]:
    
    # These fields are a 1:N for possible channel matching split on commas
    csv_profile_fields = ["interests", "programming_languages", "disciplines"]
    # These fields can only be a single channel for the item    
    flat_profile_fields = ["city", "state" ]
    
    filtered_profile_field_values = []
    
    for field in csv_profile_fields:        
        if profile.get(field) is not None:
            for word in filter(None, profile[field].lower().split(",")):
                filtered_profile_field_values.append(word)
        
    for field in flat_profile_fields:        
        word = profile.get(field)
        if word is not None:
            filtered_profile_field_values.append(word)
    
    return filtered_profile_field_values

I don't remember if the api has a way to filter some of the profile values, or request the api to split the values when they have multiple items into a list.

It would be better if we stored them in the database as a 1:N relationship and then had the api return a list in the json.

# To use more profile fields add them here.
suggestible_fields = ["city", "state", "interests", "programming_languages", "disciplines"]
data = []

for field in suggestible_fields:
if field in profile.keys() and profile[field] is not None:
if field in ["interests", "programming_languages", "disciplines"]:
for word in profile[field].lower().split(","):
data.append(word)
else:
data.append(profile[field])
return data


def build_suggestion_messages(user_id: str, channels: List[Tuple[str, str]]) -> Message:
suggestion_message = base_user_message(user_id)
message = profile_suggestion_message(channels)
suggestion_message["text"] = message
return suggestion_message