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

feat: export emails #591

Merged
merged 24 commits into from
Nov 30, 2020
Merged
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
2 changes: 2 additions & 0 deletions app/controller/command/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""Pack the modules contained in the commands directory."""
import app.controller.command.commands.team as team
import app.controller.command.commands.user as user
import app.controller.command.commands.export as export
import app.controller.command.commands.token as token
import app.controller.command.commands.project as project
import app.controller.command.commands.karma as karma
Expand All @@ -9,6 +10,7 @@

TeamCommand = team.TeamCommand
UserCommand = user.UserCommand
ExportCommand = export.ExportCommand
TokenCommand = token.TokenCommand
ProjectCommand = project.ProjectCommand
KarmaCommand = karma.KarmaCommand
Expand Down
178 changes: 178 additions & 0 deletions app/controller/command/commands/export.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
"""Command parsing for user events."""
import logging
import shlex

from argparse import ArgumentParser, _SubParsersAction
from app.controller import ResponseTuple
from app.controller.command.commands.base import Command
from db.facade import DBFacade
from app.model import User
from db.utils import get_team_by_name, get_team_members
from utils.slack_parse import check_permissions


class ExportCommand(Command):
"""Represent Export Command Parser."""

command_name = "export"
permission_error = "You do not have the sufficient " \
"permission level for this command!"
lookup_error = "Lookup error!"
no_emails_missing_msg = "All members have emails! Nice!"
char_limit_exceed_msg = "WARNING! Could not export all emails for " \
"exceeding slack character limits :("
no_user_msg = "No members found for exporting emails!"
desc = f"for dealing with {command_name}s"
# Slack currently allows to send 16000 characters max
MAX_CHAR_LIMIT = 15950

def __init__(self, db_facade: DBFacade):
"""Initialize export command."""
logging.info("Initializing ExportCommand instance")
self.parser = ArgumentParser(prog="/rocket")
self.parser.add_argument("export")
self.subparser = self.init_subparsers()
self.help = self.get_help()
self.facade = db_facade

def init_subparsers(self) -> _SubParsersAction:
"""
Initialize subparsers for export command.

:meta private:
"""
subparsers = self.parser.add_subparsers(dest="which")

# Parser for emails command
parser_view = subparsers.add_parser("emails")
parser_view.set_defaults(which="emails",
help="(Admin/Lead only) Export emails "
"of all users")
parser_view.add_argument("--team", metavar="TEAM",
type=str, action='store',
help="(Admin/Lead only) Export emails"
" by team name")

return subparsers

def get_help(self, subcommand: str = None) -> str:
"""Return command options for user events with Slack formatting."""

def get_subcommand_help(sc: str) -> str:
"""Return the help message of a specific subcommand."""
message = f"\n*{sc.capitalize()}*\n"
message += self.subparser.choices[sc].format_help()
return message

if subcommand is None or subcommand not in self.subparser.choices:
res = f"\n*{self.command_name} commands:*```"
for argument in self.subparser.choices:
res += get_subcommand_help(argument)
return res + "```"
else:
res = "\n```"
res += get_subcommand_help(subcommand)
return res + "```"

def handle(self,
command: str,
user_id: str) -> ResponseTuple:
"""Handle command by splitting into substrings and giving to parser."""
logging.debug("Handling ExportCommand")
command_arg = shlex.split(command)
args = None

try:
args = self.parser.parse_args(command_arg)
except SystemExit:
all_subcommands = list(self.subparser.choices.keys())
present_subcommands = [subcommand for subcommand in
all_subcommands
if subcommand in command_arg]
present_subcommand = None
if len(present_subcommands) == 1:
present_subcommand = present_subcommands[0]
return self.get_help(subcommand=present_subcommand), 200

if args.which == "emails":
try:
command_user = self.facade.retrieve(User, user_id)
if not check_permissions(command_user, None):
return self.permission_error, 200

# Check if team name is provided
if args.team is not None:
users = self.get_team_users(args.team)
return self.export_emails_helper(users)
else: # if team name is not provided, export all emails
users = self.facade.query(User)
return self.export_emails_helper(users)
except LookupError:
return self.lookup_error, 200
else:
return self.get_help(), 200

def get_team_users(self, team_name):
team = get_team_by_name(self.facade, team_name)
return get_team_members(self.facade, team)

def export_emails_helper(self,
users: list) -> ResponseTuple:
"""
returns emails of all users
+ names of the users who do not have an email
"""

if len(users) == 0:
return self.no_user_msg, 200

emails = []
ids_missing_emails = []

for i in range(len(users)):
if users[i].email == '':
ids_missing_emails.append(users[i].slack_id)
continue

emails.append(users[i].email)

emails_str = ",".join(emails)

if len(ids_missing_emails) != 0:
ret = "```" + emails_str + "```\n\n" \
+ "\n\nMembers who don't have an " \
"email: {}".format(
",".join(map(lambda u: f"<@{u}>", ids_missing_emails)))
if len(ret) >= self.MAX_CHAR_LIMIT:
ret = self.handle_char_limit_exceeded(ret, "\n\nMembers who")
else:
ret = "```" + emails_str + "```" \
+ "\n\n" + self.no_emails_missing_msg
if len(ret) >= self.MAX_CHAR_LIMIT:
ret = self.handle_char_limit_exceeded(
ret, self.no_emails_missing_msg)

return ret, 200

def handle_char_limit_exceeded(self, ret_str, find_str):
no-or marked this conversation as resolved.
Show resolved Hide resolved
"""
Find the last occurrence (index) of ``find_str``
and chop off items before that index

Assume that items are separated by commas.

chuck-sys marked this conversation as resolved.
Show resolved Hide resolved
:return: a string of emails with char limit exceed
warning message that is under ``MAX_CHAR_LIMIT``
"""
last_find_idx = ret_str.rfind(find_str)
temp_str1 = ret_str[:last_find_idx]
temp_str2 = ret_str[last_find_idx:]
max_end_idx = \
self.MAX_CHAR_LIMIT \
- len(temp_str2) - len(self.char_limit_exceed_msg)
temp_str3 = temp_str1[:max_end_idx]
last_comma_idx = temp_str3.rfind(',')
temp_str3 = temp_str3[:last_comma_idx]
return \
temp_str3 + "```\n\n" + temp_str2 \
+ "\n\n" + self.char_limit_exceed_msg
6 changes: 4 additions & 2 deletions app/controller/command/parser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Handle Rocket 2 commands."""
from app.controller import ResponseTuple
from app.controller.command.commands import UserCommand, TeamCommand, \
TokenCommand, ProjectCommand, KarmaCommand, MentionCommand, IQuitCommand
from app.controller.command.commands import UserCommand, TeamCommand,\
ExportCommand, TokenCommand, ProjectCommand, KarmaCommand,\
MentionCommand, IQuitCommand
from app.controller.command.commands.base import Command
from app.controller.command.commands.token import TokenCommandConfig
from db.facade import DBFacade
Expand Down Expand Up @@ -44,6 +45,7 @@ def __init__(self,
self.__github,
self.__bot,
gcp=self.__gcp)
self.commands["export"] = ExportCommand(self.__facade)
self.commands["token"] = TokenCommand(self.__facade, token_config)
self.commands["project"] = ProjectCommand(self.__facade)
self.commands["karma"] = KarmaCommand(self.__facade)
Expand Down
97 changes: 97 additions & 0 deletions tests/app/controller/command/commands/export_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
from app.controller.command.commands import ExportCommand
from unittest import TestCase
from app.model import User, Team, Permissions
from tests.memorydb import MemoryDB
from tests.util import create_test_admin


class TestExportCommand(TestCase):
def setUp(self):
self.u0 = User('U0G9QF9C6')
self.u0.email = '[email protected]'
self.u0.github_id = '305834954'

self.u1 = User('Utheomadude')
self.u1.email = '[email protected]'
self.u1.github_id = '349850564'

self.admin = create_test_admin('Uadmin')

self.lead = User('Ualley')
self.lead.email = '[email protected]'
self.lead.github_id = '2384858'
self.lead.permissions_level = Permissions.team_lead

self.t0 = Team('305849', 'butter-batter', 'Butter Batters')
self.t0.add_member(self.u0.github_id)
self.t0.add_member(self.lead.github_id)
self.t0.add_team_lead(self.lead.github_id)

self.t1 = Team('320484', 'aqua-scepter', 'Aqua Scepter')
self.t1.add_member(self.u1.github_id)

self.db = MemoryDB(users=[self.u0, self.u1, self.admin, self.lead],
teams=[self.t0, self.t1])

self.cmd = ExportCommand(self.db)

def test_get_all_emails(self):
resp, _ = self.cmd.handle('export emails', self.admin.slack_id)
self.assertIn(self.u0.email, resp)
self.assertIn(self.u1.email, resp)
self.assertIn(self.admin.email, resp)
self.assertIn(self.lead.email, resp)
self.assertIn(ExportCommand.no_emails_missing_msg, resp)

def test_lead_get_all_emails(self):
resp, _ = self.cmd.handle('export emails', self.lead.slack_id)
self.assertIn(self.u0.email, resp)
self.assertIn(self.u1.email, resp)
self.assertIn(self.admin.email, resp)
self.assertIn(self.lead.email, resp)
self.assertIn(ExportCommand.no_emails_missing_msg, resp)

def test_member_get_all_emails(self):
resp, _ = self.cmd.handle('export emails', self.u0.slack_id)
self.assertIn(ExportCommand.permission_error, resp)

def test_get_team_emails(self):
resp, _ = self.cmd.handle(
f'export emails --team {self.t0.github_team_name}',
self.admin.slack_id)
self.assertIn(self.u0.email, resp)
self.assertIn(self.lead.email, resp)
self.assertIn(ExportCommand.no_emails_missing_msg, resp)

def test_lead_get_team_emails(self):
resp, _ = self.cmd.handle(
f'export emails --team {self.t0.github_team_name}',
self.lead.slack_id)
self.assertIn(self.u0.email, resp)
self.assertIn(self.lead.email, resp)
self.assertIn(ExportCommand.no_emails_missing_msg, resp)

def test_member_get_team_emails(self):
resp, _ = self.cmd.handle(
f'export emails --team {self.t0.github_team_name}',
self.u1.slack_id)
self.assertIn(ExportCommand.permission_error, resp)

def test_lead_get_team_emails_one_missing(self):
self.u0.email = ''
resp, _ = self.cmd.handle(
f'export emails --team {self.t0.github_team_name}',
self.lead.slack_id)
self.assertIn(self.u0.slack_id, resp)
self.assertIn(self.lead.email, resp)
self.assertIn('Members who don\'t have an email:', resp)
self.assertNotIn(ExportCommand.no_emails_missing_msg, resp)

def test_get_all_emails_char_limit_reached(self):
old_lim = ExportCommand.MAX_CHAR_LIMIT
ExportCommand.MAX_CHAR_LIMIT = 30
resp, _ = self.cmd.handle('export emails', self.admin.slack_id)
self.assertIn(ExportCommand.char_limit_exceed_msg, resp)

# reset things because python doesn't do that
ExportCommand.MAX_CHAR_LIMIT = old_lim
1 change: 1 addition & 0 deletions tests/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def create_test_admin(slack_id: str) -> User:
Email [email protected]
Name Iemann Atmin
Github kibbles
Github ID 123453
Image URL https://via.placeholder.com/150
Major Computer Science
Permission Admin
Expand Down