Skip to content

Commit

Permalink
Adds donut feature to rocket
Browse files Browse the repository at this point in the history
  • Loading branch information
tarikeshaq committed Oct 12, 2020
1 parent 8f0bda7 commit bfaeb32
Show file tree
Hide file tree
Showing 7 changed files with 119 additions and 8 deletions.
7 changes: 6 additions & 1 deletion app/scheduler/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
from flask import Flask
from apscheduler.schedulers.background import BackgroundScheduler
from .modules.random_channel import RandomChannelPromoter
from .modules.donut import Donut
from .modules.base import ModuleBase
from db.facade import DBFacade
from typing import Tuple, List
from config import Config

Expand All @@ -13,10 +15,12 @@ class Scheduler:

def __init__(self,
scheduler: BackgroundScheduler,
args: Tuple[Flask, Config]):
args: Tuple[Flask, Config],
facade: DBFacade):
"""Initialize scheduler class."""
self.scheduler = scheduler
self.args = args
self.facade = facade
self.modules: List[ModuleBase] = []

self.__init_periodic_tasks()
Expand All @@ -35,3 +39,4 @@ def __add_job(self, module: ModuleBase):
def __init_periodic_tasks(self):
"""Add jobs that fire every interval."""
self.__add_job(RandomChannelPromoter(*self.args))
self.__add_job(Donut(*self.args, self.facade))
63 changes: 63 additions & 0 deletions app/scheduler/modules/donut.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Match two Launchpad member for a private conversation"""
from slack import WebClient
from interface.slack import Bot
from random import shuffle
from .base import ModuleBase
from typing import Dict, List, Tuple, Any
from flask import Flask
from config import Config
from db.facade import DBFacade
import logging


class Donut(ModuleBase):
"""Module that matches 2 launchpad members each week"""

NAME = 'Match launch pad members randomly'

def __init__(self,
flask_app: Flask,
config: Config,
facade: DBFacade):
"""Initialize the object."""
self.bot = Bot(WebClient(config.slack_api_token),
config.slack_notification_channel)
self.channel_id = self.bot.get_channel_id(config.slack_donut_channel)
self.facade = facade


def get_job_args(self) -> Dict[str, Any]:
"""Get job configuration arguments for apscheduler."""
return {'trigger': 'cron',
'minute': '*',
'name': self.NAME}

def do_it(self):
"""Pair users together, and create a private chat for them"""
users = self.bot.get_channel_users(self.channel_id)
logging.debug(f"users of the donut channel are {users}")
matched_user_pairs = self.__pair_users(users)
for pair in matched_user_pairs:
group_name = self.bot.create_private_chat(pair)
logging.info(f"The name of the created group name is {group_name}")
self.bot.send_to_channel("Hello! You have been matched by Rocket. " +
"please use this channel to get to know each other!", group_name)

def __pair_users(self, users: List[str]) -> List[List[str]]:
"""
Creates pairs of users that haven't been matched before
"""
shuffle(users)
pairs = []
pair = []
for i, user in enumerate(users):
pair.append(user)
if i % 2 != 0:
pairs.append(pair)
pair = []
# If we have an odd number of people that is not 1
# We put the odd person out in one of the groups
# So we might have a group of 3
if len(pair) == 1 and len(pairs) > 0:
pairs[len(pairs) - 1].append(pair[0])
return pairs
7 changes: 2 additions & 5 deletions app/server.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
"""Flask server instance."""
from factory import make_command_parser, make_github_webhook_handler, \
make_slack_events_handler, make_github_interface
make_slack_events_handler, make_github_interface, make_event_scheduler
from flask import Flask, request
from logging.config import dictConfig
from slackeventsapi import SlackEventAdapter
from apscheduler.schedulers.background import BackgroundScheduler
import logging
import structlog
from flask_talisman import Talisman
from config import Config
from app.scheduler import Scheduler
from interface.slack import Bot
from slack import WebClient
from boto3.session import Session
Expand Down Expand Up @@ -81,8 +79,7 @@
slack_events_adapter = SlackEventAdapter(config.slack_signing_secret,
"/slack/events",
app)
sched = Scheduler(BackgroundScheduler(timezone="America/Los_Angeles"),
(app, config))
sched = make_event_scheduler(app, config)
sched.start()

bot = Bot(WebClient(config.slack_api_token),
Expand Down
4 changes: 2 additions & 2 deletions config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Config:
'SLACK_API_TOKEN': 'slack_api_token',
'SLACK_NOTIFICATION_CHANNEL': 'slack_notification_channel',
'SLACK_ANNOUNCEMENT_CHANNEL': 'slack_announcement_channel',

'SLACK_DONUT_CHANNEL': 'slack_donut_channel',
'GITHUB_APP_ID': 'github_app_id',
'GITHUB_ORG_NAME': 'github_org_name',
'GITHUB_DEFAULT_TEAM_NAME': 'github_team_all',
Expand Down Expand Up @@ -89,7 +89,7 @@ def _set_attrs(self):
self.slack_api_token = ''
self.slack_notification_channel = ''
self.slack_announcement_channel = ''

self.slack_donut_channel = ''
self.github_app_id = ''
self.github_org_name = ''
self.github_team_all = ''
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ services:
environment:
- SLACK_NOTIFICATION_CHANNEL=${SLACK_NOTIFICATION_CHANNEL}
- SLACK_ANNOUNCEMENT_CHANNEL=${SLACK_ANNOUNCEMENT_CHANNEL}
- SLACK_DONUT_CHANNEL=${SLACK_DONUT_CHANNEL}
- SLACK_SIGNING_SECRET=${SLACK_SIGNING_SECRET}
- SLACK_API_TOKEN=${SLACK_API_TOKEN}
- GITHUB_APP_ID=${GITHUB_APP_ID}
Expand Down
8 changes: 8 additions & 0 deletions factory/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
from google.oauth2 import service_account as gcp_service_account
from googleapiclient.discovery import build as gcp_build
from typing import Optional
from flask import Flask
from app.scheduler import Scheduler
from apscheduler.schedulers.background import BackgroundScheduler


def make_dbfacade(config: Config) -> DBFacade:
Expand Down Expand Up @@ -85,6 +88,11 @@ def make_gcp_client(config: Config) -> Optional[GCPInterface]:
drive = gcp_build('drive', 'v3', credentials=credentials)
return GCPInterface(drive, subject=config.gcp_service_account_subject)

def make_event_scheduler(app: Flask, config: Config) -> Scheduler:
background_scheduler = BackgroundScheduler(timezone="America/Los_Angeles")
facade = make_dbfacade(config)
return Scheduler(background_scheduler, (app, config), facade)


def create_signing_token() -> str:
"""Create a new, random signing token."""
Expand Down
37 changes: 37 additions & 0 deletions interface/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,43 @@ def send_event_notif(self, message):
logging.error("Webhook notif failed to send due to {} error.".
format(se.error))

def create_private_chat(self, users: List[str]) -> str:
"""
Create a private chat with the given users
:param users: the list of users to add to the private chat
:raise SlackAPIError if the slack API returned error openning the chat
:return The name of of the private chat created
"""
logging.debug(f"Attempting to open a private conversation with users {users}")
response = self.sc.conversations_open(users = users)
if response['ok']:
logging.debug(f"Successfly opened a converation with the name {response['channel']['name']}")
return response['channel']['name']
raise SlackAPIError(response['error'])

def get_channel_id(self, channel_name: str) -> str:
"""
Retrieves a channel's id given it's name
:param channel_name: The name of the channel
:raise SlackAPIError if no channels were found with the name `channel_name`
:return the slack id of the channel
"""
# We strip away the "#" in case it was provided with the channel name
channel_name = channel_name.replace("#", "")
logging.debug(f"Attempting to get the id of channel {channel_name}")
channels = list(filter(lambda c: c['name'] == channel_name, self.get_channels()))
if len(channels) == 0:
raise SlackAPIError(f"No channels found with the name{channel_name}")
if len(channels) != 1:
logging.warning(f"Somehow there is more than one channel with the name {channel_name}")
return channels[0]['id']


class SlackAPIError(Exception):
"""Exception representing an error while calling Slack API."""
Expand Down

0 comments on commit bfaeb32

Please sign in to comment.