Loading cog/actions.py +25 −17 Original line number Diff line number Diff line Loading @@ -300,27 +300,26 @@ class Admin(Action): """ Schedule all sheets for update. """ cycle = cog.util.get_config("scanners", "hudson_cattle", "page", default="Cycle Unknown") prefix = "__Top Merits for {}__\n\n".format(cycle) parts = [] all_dusers = cogdb.query.all_discord_with_merits(self.session) exclude_roles = ["FRC Leadership", "Special Agent"] parts = [] dusers = reversed(sorted(all_dusers, key=lambda x: x.total_merits)) top_recruits, top_members = dusers_topn(self.msg.guild, dusers, exclude_roles, limit=self.args.limit) top_all = cogdb.query.users_with_all_merits(self.session) top_recruits, top_members = filter_top_dusers(self.msg.guild, top_all, exclude_roles, limit=self.args.limit) lines = [["Top {} Recruits".format(limit), "Merits", "Top {} Members".format(limit), "Merits"]] lines += [[rec.display_name, rec.total_merits, mem.display_name, mem.total_merits] for rec, mem in zip(top_recruits, top_members)] lines += [[rec[0], rec[1], mem[0], mem[1]] for rec, mem in zip(top_recruits, top_members)] lines += zip(top_recruits, top_members) parts += cog.tbl.format_table(lines, header=True, prefix=prefix, suffix="\n\n") dusers = reversed(sorted(all_dusers, key=lambda x: x.total_fort_merits)) top_recruits, top_members = dusers_topn(self.msg.guild, dusers, exclude_roles, limit=self.args.limit) top_fort = cogdb.query.users_with_fort_merits(self.session) top_recruits, top_members = filter_top_dusers(self.msg.guild, top_fort, exclude_roles, limit=self.args.limit) lines = [["Top {} Fort Recruits".format(limit), "Merits", "Top Fort {} Members".format(limit), "Merits"]] lines += [[rec.display_name, rec.total_fort_merits, mem.display_name, mem.total_fort_merits] for rec, mem in zip(top_recruits, top_members)] lines += [[rec[0], rec[1], mem[0], mem[1]] for rec, mem in zip(top_recruits, top_members)] parts += cog.tbl.format_table(lines, header=True, suffix="\n\n") dusers = reversed(sorted(all_dusers, key=lambda x: x.total_um_merits)) top_recruits, top_members = dusers_topn(self.msg.guild, dusers, exclude_roles, limit=self.args.limit) top_um = cogdb.query.users_with_um_merits(self.session) top_recruits, top_members = filter_top_dusers(self.msg.guild, top_um, exclude_roles, limit=self.args.limit) lines = [["Top {} UM Recruits".format(limit), "Merits", "Top UM {} Members".format(limit), "Merits"]] lines += [[rec.display_name, rec.total_um_merits, mem.display_name, mem.total_um_merits] for rec, mem in zip(top_recruits, top_members)] lines += [[rec[0], rec[1], mem[0], mem[1]] for rec, mem in zip(top_recruits, top_members)] parts += cog.tbl.format_table(lines, header=True, suffix="\n\n") for part in cog.util.merge_msgs_to_least(parts): Loading Loading @@ -1724,21 +1723,23 @@ def process_system_args(args): return system_names.split(',') def dusers_topn(guild, dusers, exclude_roles, limit=5): def filter_top_dusers(guild, dusers, exclude_roles, limit=5): """ Generate a top N list from existing DiscordUser list. Args: guild: The guild to query and get member roles from. dusers: The DiscordUser list. dusers: The DiscordUser list, of form [[DiscordUser, merits]] exclude_roles: List of roles to exclude. limit: The top limit will be generated. Default 5. Returns: [top_recruits, top_members] top_recruits: A list of form [[name, merits], [name, merits]]. top_members: A list of form [[name, merits], [name, merits]]. """ top_recruits, top_members = [], [] for duser in dusers: for duser, merits in dusers: member = guild.get_member(duser.id) if not member: continue # User left or wrong discord id Loading @@ -1748,13 +1749,20 @@ def dusers_topn(guild, dusers, exclude_roles, limit=5): if len(list(set(role_names) - set(exclude_roles))) != len(role_names): continue if "FRC Member" in role_names and len(top_members) != limit: top_members += [duser] top_members += [(duser.pref_name, merits)] elif "FRC Recruit" in role_names and len(top_recruits) != limit: top_recruits += [duser] top_recruits += [(duser.pref_name, merits)] if len(top_recruits) == limit and len(top_members) == limit: break while len(top_recruits) != limit: print(top_recruits) top_recruits += [('', '')] while len(top_members) != limit: print(top_members) top_members += [('', '')] return top_recruits, top_members Loading cogdb/query.py +50 −3 Original line number Diff line number Diff line Loading @@ -6,8 +6,10 @@ import logging import os import tempfile import sqlalchemy as sqla import sqlalchemy.exc as sqla_exc import sqlalchemy.orm.exc as sqla_oexc from sqlalchemy.sql.expression import or_ import cog.exc import cog.sheets Loading Loading @@ -99,11 +101,56 @@ def add_duser(session, member): return new_duser def all_discord_with_merits(session): def users_with_all_merits(session): """ Return the list of all current discord users that have this cycle merits. Query and return the list of DiscordUsers by their total merit contributions. Args: session: A session onto the database. Returns: A list of objects of form: [[DiscordUser, total_merits], ... ] """ return session.query(DiscordUser, (FortUser.dropped + UMUser.combo).label("total_merits")).\ join(FortUser, FortUser.name == DiscordUser.pref_name).\ join(UMUser, UMUser.name == DiscordUser.pref_name).\ filter(or_(FortUser.dropped > 0, UMUser.combo > 0)).\ order_by(sqla.desc("total_merits")).\ all() def users_with_fort_merits(session): """ Query and return the list of DiscordUsers by their total fort contributions. Args: session: A session onto the database. Returns: A list of objects of form: [[DiscordUser, fort_merits], ... ] """ return session.query(DiscordUser, FortUser.dropped).\ join(FortUser, FortUser.name == DiscordUser.pref_name).\ filter(FortUser.dropped > 0).\ order_by(FortUser.dropped.desc()).\ all() def users_with_um_merits(session): """ return [x for x in session.query(DiscordUser).all() if x.total_merits] Query and return the list of DiscordUsers by their total undermining contributions. Args: session: A session onto the database. Returns: A list of objects of form: [[DiscordUser, um_merits], ... ] """ return session.query(DiscordUser, UMUser.combo).\ join(UMUser, UMUser.name == DiscordUser.pref_name).\ filter(UMUser.combo > 0).\ order_by(UMUser.combo.desc()).\ all() def check_pref_name(session, new_name): Loading cogdb/schema.py +16 −22 Original line number Diff line number Diff line Loading @@ -86,30 +86,10 @@ class DiscordUser(Base): """ Mention this user in a response. """ return "<@{}>".format(self.id) @property @hybrid_property def total_merits(self): """ The total merits a user has done this cycle. """ return self.total_fort_merits + self.total_um_merits @property def total_fort_merits(self): """ The total merits a user has done forting this cycle. """ try: total = self.fort_user.dropped except AttributeError: total = 0 return total @property def total_um_merits(self): """ The total merits a user has done undermining cycle. """ try: total = self.um_user.held + self.um_user.redeemed except AttributeError: total = 0 return total return self.fort_user.dropped + self.um_user.combo class FortUser(Base): Loading Loading @@ -522,6 +502,20 @@ class UMUser(Base): where(UMHold.user_id == self.id).\ label('redeemed') @hybrid_property def combo(self): """ Total merits undermined by this cmdr. """ return self.held + self.redeemed @combo.expression def combo_exp(self): """ Total merits undermined by this cmdr. """ return sqla.func.sum( sqla.select([sqla.func.sum(UMHold.redeemed, UMHold.held)]). where(UMHold.user_id == self.id). label('combo') ) def __repr__(self): keys = ['id', 'name', 'row', 'cry'] kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys] Loading tests/cog/test_actions.py +0 −22 Original line number Diff line number Diff line Loading @@ -1362,25 +1362,3 @@ async def test_cmd_near_if(f_bot): actual = str(f_bot.send_message.call_args).replace("\\n", "\n") assert "LHS 397 | 19.32 | [L] Zillig Depot" in actual @pytest.mark.asyncio async def test_dusers_topn(f_dusers, f_um_testbed, f_fort_testbed): recruit_role = tc.Role("FRC Recruit") member_role = tc.Role("FRC Member") ignore_role = tc.Role("Nothing") role_map = { 1: tc.Member("Test1", [recruit_role, ignore_role]), 2: tc.Member("Test2", [member_role, ignore_role]), 3: tc.Member("Test3", [recruit_role]), } def get_member_(id): return role_map[id] fguild = aiomock.Mock() fguild.get_member = get_member_ rec, mem = cog.actions.dusers_topn(fguild, f_dusers, [], limit=5) assert rec[1].id == 3 assert mem[0].id == 2 tests/cogdb/test_query.py +18 −0 Original line number Diff line number Diff line Loading @@ -626,3 +626,21 @@ def test_track_ids_newer_than(session, f_track_testbed): date = datetime.datetime(year=2000, month=1, day=10, hour=0, minute=0, second=0, microsecond=0) objs = cogdb.query.track_ids_newer_than(session, date) assert sorted([x.id for x in objs]) == ["J3N-53B", "OVE-111"] def test_users_with_all_merits(session, f_dusers, f_fort_testbed, f_um_testbed): cap = cogdb.query.users_with_all_merits(session) assert [x[0].display_name for x in cap] == ["User1", "User2"] assert [x[1] for x in cap] == [15050, 8050] def test_users_with_fort_merits(session, f_dusers, f_fort_testbed, f_um_testbed): cap = cogdb.query.users_with_fort_merits(session) assert [x[0].display_name for x in cap] == ["User2", "User3", "User1"] assert [x[1] for x in cap] == [2000, 1800, 1100] def test_users_with_um_merits(session, f_dusers, f_fort_testbed, f_um_testbed): cap = cogdb.query.users_with_um_merits(session) assert [x[0].display_name for x in cap] == ["User1", "User2"] assert [x[1] for x in cap] == [13950, 6050] Loading
cog/actions.py +25 −17 Original line number Diff line number Diff line Loading @@ -300,27 +300,26 @@ class Admin(Action): """ Schedule all sheets for update. """ cycle = cog.util.get_config("scanners", "hudson_cattle", "page", default="Cycle Unknown") prefix = "__Top Merits for {}__\n\n".format(cycle) parts = [] all_dusers = cogdb.query.all_discord_with_merits(self.session) exclude_roles = ["FRC Leadership", "Special Agent"] parts = [] dusers = reversed(sorted(all_dusers, key=lambda x: x.total_merits)) top_recruits, top_members = dusers_topn(self.msg.guild, dusers, exclude_roles, limit=self.args.limit) top_all = cogdb.query.users_with_all_merits(self.session) top_recruits, top_members = filter_top_dusers(self.msg.guild, top_all, exclude_roles, limit=self.args.limit) lines = [["Top {} Recruits".format(limit), "Merits", "Top {} Members".format(limit), "Merits"]] lines += [[rec.display_name, rec.total_merits, mem.display_name, mem.total_merits] for rec, mem in zip(top_recruits, top_members)] lines += [[rec[0], rec[1], mem[0], mem[1]] for rec, mem in zip(top_recruits, top_members)] lines += zip(top_recruits, top_members) parts += cog.tbl.format_table(lines, header=True, prefix=prefix, suffix="\n\n") dusers = reversed(sorted(all_dusers, key=lambda x: x.total_fort_merits)) top_recruits, top_members = dusers_topn(self.msg.guild, dusers, exclude_roles, limit=self.args.limit) top_fort = cogdb.query.users_with_fort_merits(self.session) top_recruits, top_members = filter_top_dusers(self.msg.guild, top_fort, exclude_roles, limit=self.args.limit) lines = [["Top {} Fort Recruits".format(limit), "Merits", "Top Fort {} Members".format(limit), "Merits"]] lines += [[rec.display_name, rec.total_fort_merits, mem.display_name, mem.total_fort_merits] for rec, mem in zip(top_recruits, top_members)] lines += [[rec[0], rec[1], mem[0], mem[1]] for rec, mem in zip(top_recruits, top_members)] parts += cog.tbl.format_table(lines, header=True, suffix="\n\n") dusers = reversed(sorted(all_dusers, key=lambda x: x.total_um_merits)) top_recruits, top_members = dusers_topn(self.msg.guild, dusers, exclude_roles, limit=self.args.limit) top_um = cogdb.query.users_with_um_merits(self.session) top_recruits, top_members = filter_top_dusers(self.msg.guild, top_um, exclude_roles, limit=self.args.limit) lines = [["Top {} UM Recruits".format(limit), "Merits", "Top UM {} Members".format(limit), "Merits"]] lines += [[rec.display_name, rec.total_um_merits, mem.display_name, mem.total_um_merits] for rec, mem in zip(top_recruits, top_members)] lines += [[rec[0], rec[1], mem[0], mem[1]] for rec, mem in zip(top_recruits, top_members)] parts += cog.tbl.format_table(lines, header=True, suffix="\n\n") for part in cog.util.merge_msgs_to_least(parts): Loading Loading @@ -1724,21 +1723,23 @@ def process_system_args(args): return system_names.split(',') def dusers_topn(guild, dusers, exclude_roles, limit=5): def filter_top_dusers(guild, dusers, exclude_roles, limit=5): """ Generate a top N list from existing DiscordUser list. Args: guild: The guild to query and get member roles from. dusers: The DiscordUser list. dusers: The DiscordUser list, of form [[DiscordUser, merits]] exclude_roles: List of roles to exclude. limit: The top limit will be generated. Default 5. Returns: [top_recruits, top_members] top_recruits: A list of form [[name, merits], [name, merits]]. top_members: A list of form [[name, merits], [name, merits]]. """ top_recruits, top_members = [], [] for duser in dusers: for duser, merits in dusers: member = guild.get_member(duser.id) if not member: continue # User left or wrong discord id Loading @@ -1748,13 +1749,20 @@ def dusers_topn(guild, dusers, exclude_roles, limit=5): if len(list(set(role_names) - set(exclude_roles))) != len(role_names): continue if "FRC Member" in role_names and len(top_members) != limit: top_members += [duser] top_members += [(duser.pref_name, merits)] elif "FRC Recruit" in role_names and len(top_recruits) != limit: top_recruits += [duser] top_recruits += [(duser.pref_name, merits)] if len(top_recruits) == limit and len(top_members) == limit: break while len(top_recruits) != limit: print(top_recruits) top_recruits += [('', '')] while len(top_members) != limit: print(top_members) top_members += [('', '')] return top_recruits, top_members Loading
cogdb/query.py +50 −3 Original line number Diff line number Diff line Loading @@ -6,8 +6,10 @@ import logging import os import tempfile import sqlalchemy as sqla import sqlalchemy.exc as sqla_exc import sqlalchemy.orm.exc as sqla_oexc from sqlalchemy.sql.expression import or_ import cog.exc import cog.sheets Loading Loading @@ -99,11 +101,56 @@ def add_duser(session, member): return new_duser def all_discord_with_merits(session): def users_with_all_merits(session): """ Return the list of all current discord users that have this cycle merits. Query and return the list of DiscordUsers by their total merit contributions. Args: session: A session onto the database. Returns: A list of objects of form: [[DiscordUser, total_merits], ... ] """ return session.query(DiscordUser, (FortUser.dropped + UMUser.combo).label("total_merits")).\ join(FortUser, FortUser.name == DiscordUser.pref_name).\ join(UMUser, UMUser.name == DiscordUser.pref_name).\ filter(or_(FortUser.dropped > 0, UMUser.combo > 0)).\ order_by(sqla.desc("total_merits")).\ all() def users_with_fort_merits(session): """ Query and return the list of DiscordUsers by their total fort contributions. Args: session: A session onto the database. Returns: A list of objects of form: [[DiscordUser, fort_merits], ... ] """ return session.query(DiscordUser, FortUser.dropped).\ join(FortUser, FortUser.name == DiscordUser.pref_name).\ filter(FortUser.dropped > 0).\ order_by(FortUser.dropped.desc()).\ all() def users_with_um_merits(session): """ return [x for x in session.query(DiscordUser).all() if x.total_merits] Query and return the list of DiscordUsers by their total undermining contributions. Args: session: A session onto the database. Returns: A list of objects of form: [[DiscordUser, um_merits], ... ] """ return session.query(DiscordUser, UMUser.combo).\ join(UMUser, UMUser.name == DiscordUser.pref_name).\ filter(UMUser.combo > 0).\ order_by(UMUser.combo.desc()).\ all() def check_pref_name(session, new_name): Loading
cogdb/schema.py +16 −22 Original line number Diff line number Diff line Loading @@ -86,30 +86,10 @@ class DiscordUser(Base): """ Mention this user in a response. """ return "<@{}>".format(self.id) @property @hybrid_property def total_merits(self): """ The total merits a user has done this cycle. """ return self.total_fort_merits + self.total_um_merits @property def total_fort_merits(self): """ The total merits a user has done forting this cycle. """ try: total = self.fort_user.dropped except AttributeError: total = 0 return total @property def total_um_merits(self): """ The total merits a user has done undermining cycle. """ try: total = self.um_user.held + self.um_user.redeemed except AttributeError: total = 0 return total return self.fort_user.dropped + self.um_user.combo class FortUser(Base): Loading Loading @@ -522,6 +502,20 @@ class UMUser(Base): where(UMHold.user_id == self.id).\ label('redeemed') @hybrid_property def combo(self): """ Total merits undermined by this cmdr. """ return self.held + self.redeemed @combo.expression def combo_exp(self): """ Total merits undermined by this cmdr. """ return sqla.func.sum( sqla.select([sqla.func.sum(UMHold.redeemed, UMHold.held)]). where(UMHold.user_id == self.id). label('combo') ) def __repr__(self): keys = ['id', 'name', 'row', 'cry'] kwargs = ['{}={!r}'.format(key, getattr(self, key)) for key in keys] Loading
tests/cog/test_actions.py +0 −22 Original line number Diff line number Diff line Loading @@ -1362,25 +1362,3 @@ async def test_cmd_near_if(f_bot): actual = str(f_bot.send_message.call_args).replace("\\n", "\n") assert "LHS 397 | 19.32 | [L] Zillig Depot" in actual @pytest.mark.asyncio async def test_dusers_topn(f_dusers, f_um_testbed, f_fort_testbed): recruit_role = tc.Role("FRC Recruit") member_role = tc.Role("FRC Member") ignore_role = tc.Role("Nothing") role_map = { 1: tc.Member("Test1", [recruit_role, ignore_role]), 2: tc.Member("Test2", [member_role, ignore_role]), 3: tc.Member("Test3", [recruit_role]), } def get_member_(id): return role_map[id] fguild = aiomock.Mock() fguild.get_member = get_member_ rec, mem = cog.actions.dusers_topn(fguild, f_dusers, [], limit=5) assert rec[1].id == 3 assert mem[0].id == 2
tests/cogdb/test_query.py +18 −0 Original line number Diff line number Diff line Loading @@ -626,3 +626,21 @@ def test_track_ids_newer_than(session, f_track_testbed): date = datetime.datetime(year=2000, month=1, day=10, hour=0, minute=0, second=0, microsecond=0) objs = cogdb.query.track_ids_newer_than(session, date) assert sorted([x.id for x in objs]) == ["J3N-53B", "OVE-111"] def test_users_with_all_merits(session, f_dusers, f_fort_testbed, f_um_testbed): cap = cogdb.query.users_with_all_merits(session) assert [x[0].display_name for x in cap] == ["User1", "User2"] assert [x[1] for x in cap] == [15050, 8050] def test_users_with_fort_merits(session, f_dusers, f_fort_testbed, f_um_testbed): cap = cogdb.query.users_with_fort_merits(session) assert [x[0].display_name for x in cap] == ["User2", "User3", "User1"] assert [x[1] for x in cap] == [2000, 1800, 1100] def test_users_with_um_merits(session, f_dusers, f_fort_testbed, f_um_testbed): cap = cogdb.query.users_with_um_merits(session) assert [x[0].display_name for x in cap] == ["User1", "User2"] assert [x[1] for x in cap] == [13950, 6050]