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

Gcal Integration #378

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ db.sqlite3
venv
staticfiles/*
!staticfiles/.gitignore
.env
.envrc
.direnv

Expand Down
72 changes: 70 additions & 2 deletions bot/processors/pennychat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
post_organizer_edit_after_share_blocks,
share_penny_chat_invitation,
)
from bot.utils import chat_postEphemeral_with_fallback
from bot.utils import chat_postEphemeral_with_fallback, build_share_string
from integrations.google import build_credentials, GoogleCalendar, get_authorization_url
from integrations.models import GoogleCredentials
from pennychat.models import (
PennyChat,
PennyChatSlackInvitation,
Expand All @@ -26,11 +28,13 @@
VIEW_SUBMISSION = 'view_submission'
VIEW_CLOSED = 'view_closed'

ADD_GOOGLE_INTEGRATION = 'add_google_integration'
PENNY_CHAT_DATE = 'penny_chat_date'
PENNY_CHAT_TIME = 'penny_chat_time'
PENNY_CHAT_USER_SELECT = 'penny_chat_user_select'
PENNY_CHAT_CHANNEL_SELECT = 'penny_chat_channel_select'
PENNY_CHAT_DETAILS = 'penny_chat_details'
PENNY_CHAT_REVIEW_DETAILS = 'penny_chat_review_details'
PENNY_CHAT_EDIT = 'penny_chat_edit'
PENNY_CHAT_SHARE = 'penny_chat_share'
PENNY_CHAT_CAN_ATTEND = 'penny_chat_can_attend'
Expand Down Expand Up @@ -85,7 +89,7 @@ def penny_chat_details_modal(penny_chat_invitation):
},
'submit': {
'type': 'plain_text',
'text': 'Share Invite :the_horns:',
'text': 'Submit :floppy_disk:',
},
'blocks': [
{
Expand Down Expand Up @@ -231,6 +235,40 @@ def penny_chat_details_modal(penny_chat_invitation):
return template


def add_google_integration_modal(authorization_url):
template = {
'type': 'modal',
'notify_on_close': True,
'callback_id': "google_auth_callback",
'title': {
'type': 'plain_text',
'text': 'Penny Chat Details'
},
'blocks': [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Click the button to activate the Google Calendar integration."
},
"accessory": {
"type": "button",
"text": {
"type": "plain_text",
"text": "Add Google Integration",
"emoji": True
},
"value": "add_integration",
"url": authorization_url,
"action_id": ADD_GOOGLE_INTEGRATION
}
}
]
}

return template


class PennyChatBotModule(BotModule):
"""Responsible for all interactions related to the `/penny chat` command.

Expand Down Expand Up @@ -302,6 +340,12 @@ def create_penny_chat(cls, slack, event):
penny_chat_invitation.view = response.data['view']['id']
penny_chat_invitation.save()

@classmethod
def integrate_google_calendar(cls, slack, event):
user = get_or_create_social_profile_from_slack_id(event['user_id'])
modal = add_google_integration_modal(authorization_url=get_authorization_url(user.email))
slack.views_open(view=modal, trigger_id=event['trigger_id'])

@is_block_interaction_event
@has_action_id(PENNY_CHAT_SCHEDULE_MATCH)
def schedule_match(self, event):
Expand Down Expand Up @@ -379,6 +423,18 @@ def visibility_select(self, event):
penny_chat_invitation.visibility = int(selected_visibility)
penny_chat_invitation.save()

def add_google_meet(self, penny_chat_invitation):
user = get_or_create_social_profile_from_slack_id(penny_chat_invitation.organizer_slack_id).user
google_credentials = GoogleCredentials.objects.get(user=user)
credentials = build_credentials(google_credentials)
calendar = GoogleCalendar(credentials=credentials)

return calendar.create_event(
summary=penny_chat_invitation.title,
description=penny_chat_invitation.description,
start=penny_chat_invitation.date
)

@has_event_type([VIEW_SUBMISSION, VIEW_CLOSED])
@has_callback_id(PENNY_CHAT_DETAILS)
def submit_details_and_share(self, event):
Expand All @@ -403,6 +459,18 @@ def submit_details_and_share(self, event):
}
}

penny_chat_invitation.save_organizer_from_slack_id(penny_chat_invitation.organizer_slack_id)

user = get_or_create_social_profile_from_slack_id(penny_chat_invitation.organizer_slack_id).user

if user.google_credentials is None:
update_modal = add_google_integration_modal(view['id'])
response = self.slack_client.views_push(view=update_modal, trigger_id=event['trigger_id'])

if not penny_chat_invitation.video_conference_link:
meet = self.add_google_meet(penny_chat_invitation)
penny_chat_invitation.video_conference_link = meet['hangoutLink']

# Ready to share
penny_chat_invitation.status = PennyChatSlackInvitation.SHARED
penny_chat_invitation.save()
Expand Down
70 changes: 44 additions & 26 deletions bot/tasks/pennychat.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
from pytz import timezone, utc
from sentry_sdk import capture_exception

from bot.utils import build_share_string, comma_split
from common.utils import get_slack_client
from pennychat.models import PennyChatSlackInvitation, Participant
from users.models import (
SocialProfile,
get_or_create_social_profile_from_slack_id,
get_or_create_social_profile_from_slack_ids,
)

VIEW_SUBMISSION = 'view_submission'
Expand Down Expand Up @@ -197,11 +197,13 @@ def _penny_chat_details_blocks(penny_chat_invitation, mode=None):
if include_calendar_link:
start_date = penny_chat_invitation.date.astimezone(utc).strftime('%Y%m%dT%H%M%SZ')
end_date = (penny_chat_invitation.date.astimezone(utc) + timedelta(hours=1)).strftime('%Y%m%dT%H%M%SZ')

description = f'{penny_chat_invitation.description} [Video Link]({penny_chat_invitation.video_conference_link})'
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe add a new line? ...description}\n[Video...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Lol, I totally forgot to revisit this. Adding to todos...

google_cal_url = 'https://calendar.google.com/calendar/render?' \
'action=TEMPLATE&text=' \
f'{urllib.parse.quote(penny_chat_invitation.title)}&dates=' \
f'{start_date}/{end_date}&details=' \
f'{urllib.parse.quote(penny_chat_invitation.description)}'
f'{urllib.parse.quote(description)}'

date_time_block['accessory'] = {
'type': 'button',
Expand Down Expand Up @@ -240,6 +242,45 @@ def _penny_chat_details_blocks(penny_chat_invitation, mode=None):
date_time_block
]

if penny_chat_invitation.video_conference_link:
Copy link
Collaborator

Choose a reason for hiding this comment

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

probably a nit pick, but rather than have 3 sections here, I'd just have a {PREVIEW, INVITE, UPDATE} section with a parenthetical (A video link will be provided shortly before the chat starts) and a REMIND section with the full details.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So just remove the Video Link header from the invite and add parentheses?

Copy link
Collaborator

Choose a reason for hiding this comment

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

yup, just like you did - looks good

body.append(
{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': f'*Video Call Link*'
}
}
)
if mode in {PREVIEW, INVITE, UPDATE}:
body.append(
{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': f'A video link will be provided shortly before the chat starts'
}
}
)
elif mode in {REMIND}:
body.append(
{
'type': 'actions',
'elements': [
{
'type': 'button',
'text': {
'type': 'plain_text',
'text': 'Join Video Call :call_me_hand:',
'emoji': True,
},
'url': penny_chat_invitation.video_conference_link,
'style': 'primary',
}
]
}
)

if include_rsvp:
body.append(
{
Expand Down Expand Up @@ -368,25 +409,7 @@ def _followup_reminder_blocks(penny_chat_invitation):


def organizer_edit_after_share_blocks(slack_client, penny_chat_invitation):
shares = []
users = get_or_create_social_profile_from_slack_ids(
comma_split(penny_chat_invitation.invitees),
slack_client=slack_client,
)
for slack_user_id in comma_split(penny_chat_invitation.invitees):
shares.append(users[slack_user_id].real_name)

if len(penny_chat_invitation.channels) > 0:
for channel in comma_split(penny_chat_invitation.channels):
shares.append(f'<#{channel}>')

if len(shares) == 1:
share_string = shares[0]
elif len(shares) == 2:
share_string = ' and '.join(shares)
elif len(shares) > 2:
shares[-1] = f'and {shares[-1]}'
share_string = ', '.join(shares)
share_string = build_share_string(slack_client, penny_chat_invitation)

shared_message_preview_blocks = _penny_chat_details_blocks(penny_chat_invitation, mode=PREVIEW) + [
{
Expand Down Expand Up @@ -422,8 +445,3 @@ def organizer_edit_after_share_blocks(slack_client, penny_chat_invitation):
]

return shared_message_preview_blocks


def comma_split(comma_delimited_string):
"""normal string split for ''.split(',') returns [''], so using this instead"""
return [x for x in comma_delimited_string.split(',') if x]
33 changes: 33 additions & 0 deletions bot/utils.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import logging

from django.conf import settings

from common.utils import get_slack_client
from sentry_sdk import capture_exception
from slack.errors import SlackApiError

from users.models import get_or_create_social_profile_from_slack_ids

_CHANNEL_NAME__ID = None


Expand All @@ -27,6 +30,36 @@ def notify_admins(slack_client, message):
pass


def comma_split(comma_delimited_string):
"""normal string split for ''.split(',') returns [''], so using this instead"""
return [x for x in comma_delimited_string.split(',') if x]


def build_share_string(slack_client, penny_chat_invitation):
shares = []
users = get_or_create_social_profile_from_slack_ids(
comma_split(penny_chat_invitation.invitees),
slack_client=slack_client,
)
for slack_user_id in comma_split(penny_chat_invitation.invitees):
shares.append(users[slack_user_id].real_name)

if len(penny_chat_invitation.channels) > 0:
for channel in comma_split(penny_chat_invitation.channels):
shares.append(f'<#{channel}>')

share_string = ''
if len(shares) == 1:
share_string = shares[0]
elif len(shares) == 2:
share_string = ' and '.join(shares)
elif len(shares) > 2:
shares[-1] = f'and {shares[-1]}'
share_string = ', '.join(shares)

return share_string


def chat_postEphemeral_with_fallback(slack_client, channel, user, blocks=None, text=None):
try:
slack_client.chat_postEphemeral(channel=channel, user=user, blocks=blocks, text=text)
Expand Down
2 changes: 2 additions & 0 deletions bot/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def command(request):
command = event['text'].split(' ', 1)[0]
if command == 'chat':
PennyChatBotModule.create_penny_chat(slack_client, event)
elif command == 'gcal':
PennyChatBotModule.integrate_google_calendar(slack_client, event)
elif command == 'set-topic':
MatchMakingBotModule.set_topic_channel(slack_client, event)
else:
Expand Down
Loading