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
4 changes: 3 additions & 1 deletion bot/processors/pennychat.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
post_organizer_edit_after_share_blocks,
share_penny_chat_invitation,
add_google_meet,
add_google_integration_blocks,
add_google_integration_blocks, update_google_meet,
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit, new line

)
from bot.utils import chat_postEphemeral_with_fallback
from integrations.google import get_authorization_url
Expand Down Expand Up @@ -424,6 +424,8 @@ def submit_details_and_share(self, event):

if not penny_chat_invitation.video_conference_link:
add_google_meet(penny_chat_invitation.id)
else:
update_google_meet(penny_chat_invitation.id)
share_penny_chat_invitation(penny_chat_invitation.id)

@is_block_interaction_event
Expand Down
69 changes: 43 additions & 26 deletions bot/tasks/pennychat.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from django.conf import settings
from pytz import timezone, utc
from sentry_sdk import capture_exception
from slack.errors import SlackApiError

from common.utils import get_slack_client
from integrations.google import build_credentials, get_authorization_url, GoogleCalendar
Expand Down Expand Up @@ -67,37 +66,57 @@ def post_organizer_edit_after_share_blocks(penny_chat_view_id):
)


@background
def add_google_meet(penny_chat_id):
slack_client = get_slack_client()
penny_chat_invitation = PennyChatSlackInvitation.objects.get(id=penny_chat_id)
user = None
def get_user_google_calendar_from_slack_id(slack_id):
user = get_or_create_social_profile_from_slack_id(slack_id).user
try:
user = get_or_create_social_profile_from_slack_id(penny_chat_invitation.organizer_slack_id).user
google_credentials = GoogleCredentials.objects.get(user=user)
except GoogleCredentials.DoesNotExist:
authorization_url = get_authorization_url(user)
slack_client = get_slack_client()
slack_client.chat_postMessage(
channel=penny_chat_invitation.organizer_slack_id,
channel=slack_id,
blocks=add_google_integration_blocks(authorization_url, from_penny_chat=True),
)
return
except SlackApiError:
return

credentials = build_credentials(google_credentials)
calendar = GoogleCalendar(credentials=credentials)
return GoogleCalendar(credentials=credentials)


@background
def add_google_meet(penny_chat_id):
penny_chat_invitation = PennyChatSlackInvitation.objects.get(id=penny_chat_id)

calendar = get_user_google_calendar_from_slack_id(penny_chat_invitation.organizer_slack_id)

if calendar is None:
return

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

penny_chat_invitation.video_conference_link = meet['hangoutLink']
penny_chat_invitation.video_conference_link = meet.get('hangoutLink')
penny_chat_invitation.google_event_id = meet.get('id')
penny_chat_invitation.save()


@background
def update_google_meet(penny_chat_id):
penny_chat_invitation = PennyChatSlackInvitation.objects.get(id=penny_chat_id)

calendar = get_user_google_calendar_from_slack_id(penny_chat_invitation.organizer_slack_id)
Copy link
Collaborator

Choose a reason for hiding this comment

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

if the user retracts google permissions then calendar will be None here, add an if calendar is None here or we'll error on line 112. In this case I guess we just keep the calendar invite as is? (I presume it will still work)


calendar.update_event(
event_id=penny_chat_invitation.google_event_id,
summary=penny_chat_invitation.title,
description=penny_chat_invitation.description,
start=penny_chat_invitation.date
)


@background
def share_penny_chat_invitation(penny_chat_id):
"""Shares penny chat invitations with people and channels in the invitee list."""
Expand Down Expand Up @@ -261,7 +280,7 @@ def _penny_chat_details_blocks(penny_chat_invitation, mode=None):
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})'
description = f'{penny_chat_invitation.description}\nVideo Link: {penny_chat_invitation.video_conference_link}'
google_cal_url = 'https://calendar.google.com/calendar/render?' \
'action=TEMPLATE&text=' \
f'{urllib.parse.quote(penny_chat_invitation.title)}&dates=' \
Expand Down Expand Up @@ -306,27 +325,25 @@ def _penny_chat_details_blocks(penny_chat_invitation, mode=None):
]

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': '*Video Call Link*'
}
}
)
if mode in {PREVIEW, INVITE, UPDATE}:
body.append(
{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': 'A video link will be provided shortly before the chat starts'
'text': '_(A video link will be provided shortly before the chat starts)_'
}
}
)
elif mode in {REMIND}:
body.append(
body += [
{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': '*Video Call Link*'
}
},
{
'type': 'actions',
'elements': [
Expand All @@ -342,7 +359,7 @@ def _penny_chat_details_blocks(penny_chat_invitation, mode=None):
}
]
}
)
]

if include_rsvp:
body.append(
Expand Down Expand Up @@ -533,7 +550,7 @@ def missing_google_auth_blocks():


def add_google_integration_blocks(authorization_url, from_penny_chat=False):
pre_add_button_blocks = missing_google_auth_blocks() if from_penny_chat else None
pre_add_button_blocks = missing_google_auth_blocks() if from_penny_chat else []
blocks = pre_add_button_blocks + [
{
'type': 'section',
Expand Down
1 change: 1 addition & 0 deletions integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
default_app_config = 'integrations.apps.IntegrationsConfig'
3 changes: 3 additions & 0 deletions integrations/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@

class IntegrationsConfig(AppConfig):
name = 'integrations'

def ready(self):
import integrations.signals # noqa
26 changes: 14 additions & 12 deletions integrations/google.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from django.utils.http import urlsafe_base64_encode
from google_auth_oauthlib.flow import Flow
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError
from google.oauth2.credentials import Credentials

from django.conf import settings
Expand All @@ -16,10 +15,7 @@ class GoogleCalendar:
def __init__(self, credentials, calendar_id='primary'):
self.service = build('calendar', 'v3', credentials=credentials)
self.calendar_id = calendar_id

@property
def events(self):
return self.service.events()
self.events = self.service.events()

def add_conference_call_to_event(self, event_id):
event_patch = {
Expand All @@ -39,7 +35,8 @@ def add_conference_call_to_event(self, event_id):
# Fetch updated event and return it
return self.events.get(calendarId=self.calendar_id, eventId=event_id).execute()

def create_event(self, summary, description, start, end=None, with_meet=True):
@staticmethod
def build_event_data(summary, description, start, end):
if not end:
end = start + timedelta(hours=1)
data = {
Expand All @@ -52,16 +49,21 @@ def create_event(self, summary, description, start, end=None, with_meet=True):
'dateTime': end.isoformat(),
}
}
try:
event_data = self.events.insert(calendarId='primary', body=data).execute()
return data

if with_meet:
event_data = self.add_conference_call_to_event(event_id=event_data['id'])
except HttpError as e:
print(e)
def create_event(self, summary, description, start, end=None, with_meet=True):
data = GoogleCalendar.build_event_data(summary, description, start, end)
event_data = self.events.insert(calendarId='primary', body=data).execute()

if with_meet:
event_data = self.add_conference_call_to_event(event_id=event_data['id'])

return event_data

def update_event(self, event_id, summary, description, start, end=None):
data = GoogleCalendar.build_event_data(summary, description, start, end)
self.events.patch(calendarId='primary', eventId=event_id, body=data).execute()


def get_google_flow():
client_secrets = {
Expand Down
18 changes: 18 additions & 0 deletions integrations/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils import timezone

from bot.tasks import add_google_meet
from integrations.models import GoogleCredentials
from pennychat.models import PennyChatSlackInvitation


@receiver(post_save, sender=GoogleCredentials)
def add_google_meet_to_upcoming_chats(sender, **kwargs):
credentials = kwargs.get('instance')
if credentials:
user = credentials.user
slack_ids = [profile.slack_id for profile in user.social_profiles.all()]
invites = PennyChatSlackInvitation.objects.filter(organizer_slack_id__in=slack_ids, date__gt=timezone.now())
Copy link
Collaborator

Choose a reason for hiding this comment

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

would there ever be any invites with date__gt=timezone.now() ? is this a bug

Copy link
Collaborator

Choose a reason for hiding this comment

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

disregard ... hey this box wine is great!

for invite in invites:
add_google_meet(invite.id)
18 changes: 18 additions & 0 deletions pennychat/migrations/0005_pennychat_google_event_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.2.13 on 2020-11-18 01:20

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('pennychat', '0004_pennychat_video_conference_link'),
]

operations = [
migrations.AddField(
model_name='pennychat',
name='google_event_id',
field=models.TextField(null=True),
),
]
1 change: 1 addition & 0 deletions pennychat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class PennyChat(models.Model):
created_from_slack_team_id = models.CharField(max_length=20, null=True)
visibility = models.IntegerField(choices=VISIBILITY_CHOICES, default=PUBLIC)
video_conference_link = models.TextField(null=True)
google_event_id = models.TextField(null=True)

# meta
created = models.DateTimeField(auto_now_add=True)
Expand Down