Loading cog/actions.py +25 −0 Original line number Diff line number Diff line Loading @@ -541,9 +541,14 @@ class Admin(Action): if values: cogdb.query.um_add_system_targets(self.session, values) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='um', ) um_sheet = await um_scanner.get_batch(['D1:13'], 'COLUMNS', 'FORMULA') data = cogdb.scanners.UMScanner.slide_templates(um_sheet, values) await um_scanner.send_batch(data, input_opt='USER_ENTERED') record.flushed_sheet = True msgs = cog.util.merge_msgs_to_least(msgs) for msg in cog.util.merge_msgs_to_least(msgs): Loading Loading @@ -927,6 +932,10 @@ class Drop(Action): await check_sheet(client=self, scanner_name='hudson_cattle', attr='fort_user', user_cls=FortUser) drop = cogdb.query.fort_add_drop(self.session, system=system, user=self.duser.fort_user, amount=self.args.amount) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='fort', ) if self.args.set: system.set_status(self.args.set) Loading @@ -942,6 +951,7 @@ class Drop(Action): ) scanner = get_scanner("hudson_cattle") await scanner.send_batch(self.payloads) record.flushed_sheet = True self.log.info('DROP %s - Sucessfully dropped %d at %s.', self.duser.display_name, self.args.amount, system.name) Loading Loading @@ -1003,6 +1013,10 @@ class Fort(Action): system = cogdb.query.fort_find_system(self.session, system_name) system.set_status(self.args.set) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='fort', ) self.session.commit() self.payloads += cogdb.scanners.FortScanner.update_system_dict( Loading @@ -1010,6 +1024,7 @@ class Fort(Action): ) scanner = get_scanner("hudson_cattle") await scanner.send_batch(self.payloads) record.flushed_sheet = True return system.display() Loading Loading @@ -1243,6 +1258,10 @@ class Hold(Action): await self.check_sheet_user() holds, response = await self.set_hold() record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='um' if self.args.sheet_src == EUMSheet.main else "snipe" ) self.session.commit() for hold in holds: Loading @@ -1251,6 +1270,7 @@ class Hold(Action): scanner = get_scanner("hudson_undermine" if self.args.sheet_src == EUMSheet.main else "hudson_snipe") await scanner.send_batch(self.payloads) record.flushed_sheet = True await self.bot.send_message(self.msg.channel, response) Loading Loading @@ -1919,6 +1939,10 @@ class UM(Action): system = cogdb.query.um_find_system(self.session, ' '.join(self.args.system), sheet_src=self.args.sheet_src) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='um' if self.args.sheet_src == EUMSheet.main else "snipe" ) if self.args.offset: system.map_offset = self.args.offset if self.args.priority: Loading @@ -1941,6 +1965,7 @@ class UM(Action): self.session.commit() scanner = get_scanner("hudson_undermine" if self.args.sheet_src == EUMSheet.main else "hudson_snipe") await scanner.send_batch(self.payloads) record.flushed_sheet = True response = system.display() Loading cog/util.py +15 −4 Original line number Diff line number Diff line Loading @@ -182,15 +182,26 @@ class ReprMixin(): class TimestampMixin(): """ Simple mixing that converts updated_at timestamp to a datetime object. Timestamps on object are assumed to be created as UTC timestamps. """ @property def utc_date(self): """Assumes the timestampe is in fact UTC, native datetime with no tz info. """ def created_date(self): """The created at date as a naive datetime object.""" return datetime.datetime.utcfromtimestamp(self.created_at) @property def created_date_tz(self): """The created at date as a timezone aware datetime object.""" return datetime.datetime.fromtimestamp(self.created_at, tz=datetime.timezone.utc) @property def updated_date(self): """The update at date as a naive datetime object.""" return datetime.datetime.utcfromtimestamp(self.updated_at) @property def utc_date_tz(self): """Assumes the timestampe is in fact UTC, datetime object set to UTC timezone.""" def updated_date_tz(self): """The update at date as a timezone aware datetime object.""" return datetime.datetime.fromtimestamp(self.updated_at, tz=datetime.timezone.utc) Loading cogdb/query.py +46 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,8 @@ from cogdb.schema import (DiscordUser, FortSystem, FortPrep, FortDrop, FortUser, EFortType, UMSystem, UMUser, UMHold, EUMSheet, EUMType, KOS, AdminPerm, ChannelPerm, RolePerm, TrackSystem, TrackSystemCached, TrackByID, Global, Vote, EVoteType, Consolidation) Global, Vote, EVoteType, Consolidation, ESheetType, SheetRecord) def dump_db(session): # pragma: no cover Loading Loading @@ -1395,3 +1396,47 @@ def route_systems(eddb_session, systems): mapped_originals = {x.name: x for x in systems} _, routed_systems = cogdb.eddb.find_route_closest_hq(eddb_session, [x.name for x in systems]) return [mapped_originals[x.name].display() for x in routed_systems] def add_sheet_record(session, **kwargs): """ Add a permanent record of a change to the sheets and db. The main purpose is historical to be able to record important commands. Args: session: A session onto the db. discord_id: The discord id of the requesting user. channel_id: The channel id where message was sent. command: The text of the message user sent. sheet_src: The sheet being modified, one of: ['fort', 'um', 'snipe'] Returns: The added SheetRecord """ record = SheetRecord(**kwargs) session.add(record) return record def get_user_sheet_records(session, *, discord_id, cycle=None): """ Get sheet records for a particular cycle and user, a way to see what changes user has requested for bot. Args: session: A session onto the db. discord_id: The discord id of the requesting user. cycle: The cycle to pull information for. Default is current cycle. Returns: All SheetRecords matching, empty list if none found. """ if not cycle: cycle = cog.util.current_cycle() return session.query(SheetRecord).\ filter(SheetRecord.discord_id == discord_id, SheetRecord.cycle == cycle).\ order_by(SheetRecord.created_at).\ all() cogdb/scanners.py +1 −1 Original line number Diff line number Diff line Loading @@ -1075,7 +1075,7 @@ class BGSDemo(FortScanner): ", ".join([x.state.text for x in entry.pending_states]), entry.happiness.text, entry.influence, str(entry.utc_date), str(entry.updated_date), ]] end_row = row + len(values) Loading cogdb/schema.py +42 −4 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ These allow the bot to store and query the information in sheets that are parsed """ import datetime import enum import time import sqlalchemy as sqla import sqlalchemy.orm as sqla_orm Loading @@ -16,7 +17,7 @@ import cog.exc import cog.tbl import cog.util import cogdb from cog.util import ReprMixin from cog.util import ReprMixin, TimestampMixin LEN_CMD = 25 # Max length of a subclass of cog.actions Loading @@ -24,6 +25,7 @@ LEN_NAME = 100 LEN_REASON = 400 LEN_SHEET_COL = 5 LEN_CARRIER = 7 LEN_COMMAND = 2000 MAX_VOTE_VALUE = 50 EVENT_CARRIER = """ CREATE EVENT IF NOT EXISTS clean_carriers Loading Loading @@ -52,7 +54,7 @@ class DiscordUser(ReprMixin, Base): _repr_keys = ['id', 'display_name', 'pref_name', 'pref_cry'] id = sqla.Column(sqla.BigInteger, primary_key=True) # Discord id display_name = sqla.Column(sqla.String(LEN_NAME)) display_name = sqla.Column(sqla.String(LEN_NAME)) # FIXME: Remove this pref_name = sqla.Column(sqla.String(LEN_NAME), index=True, nullable=False) # pref_name == display_name until change pref_cry = sqla.Column(sqla.String(LEN_NAME), default='') Loading Loading @@ -1245,7 +1247,7 @@ class Consolidation(ReprMixin, Base): Track the consolidation vote changes over time. """ __tablename__ = 'consolidation_tracker' _repr_keys = ['id', 'amount', 'cons_total', 'prep_total', 'updated_at'] _repr_keys = ['id', 'cycle', 'amount', 'cons_total', 'prep_total', 'updated_at'] id = sqla.Column(sqla.BigInteger, primary_key=True) amount = sqla.Column(sqla.Integer, default=0) Loading Loading @@ -1284,6 +1286,42 @@ class Consolidation(ReprMixin, Base): return value class ESheetType(enum.Enum): """ Type of sheet the transaction modified. """ fort = 1 um = 2 snipe = 3 class SheetRecord(ReprMixin, TimestampMixin, Base): """ For every command modifying the local database and sheet, record a transaction. """ __tablename__ = 'history_sheet_transactions' _repr_keys = ['id', 'discord_id', 'channel_id', 'sheet_src', 'cycle', 'command', 'flushed_sheet', 'created_at'] id = sqla.Column(sqla.BigInteger, primary_key=True) discord_id = sqla.Column(sqla.BigInteger, nullable=False) channel_id = sqla.Column(sqla.BigInteger, nullable=False) sheet_src = sqla.Column(sqla.Enum(ESheetType), default=ESheetType.fort) cycle = sqla.Column(sqla.Integer, default=cog.util.current_cycle) command = sqla.Column(sqla.String(LEN_COMMAND), default="") flushed_sheet = sqla.Column(sqla.Boolean, default=False) created_at = sqla.Column(sqla.Integer, default=time.time) # Relationships user = sqla_orm.relationship('DiscordUser', uselist=False, viewonly=True, lazy='joined', primaryjoin='foreign(SheetRecord.discord_id) == DiscordUser.id') def __eq__(self, other): return (isinstance(self, SheetRecord) and isinstance(other, SheetRecord) and self.id == other.id) def __hash__(self): return hash(self.id) def kwargs_um_system(cells, sheet_col, *, sheet_src=EUMSheet.main): """ Return keyword args parsed from cell frame. Loading Loading @@ -1424,7 +1462,7 @@ def empty_tables(session, *, perm=False): """ Drop all tables. """ classes = [FortDrop, UMHold, FortSystem, UMSystem, FortUser, UMUser, KOS, classes = [SheetRecord, FortDrop, UMHold, FortSystem, UMSystem, FortUser, UMUser, KOS, KOS, TrackSystem, TrackSystemCached, TrackByID, AdminPerm, ChannelPerm, RolePerm] if perm: Loading Loading
cog/actions.py +25 −0 Original line number Diff line number Diff line Loading @@ -541,9 +541,14 @@ class Admin(Action): if values: cogdb.query.um_add_system_targets(self.session, values) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='um', ) um_sheet = await um_scanner.get_batch(['D1:13'], 'COLUMNS', 'FORMULA') data = cogdb.scanners.UMScanner.slide_templates(um_sheet, values) await um_scanner.send_batch(data, input_opt='USER_ENTERED') record.flushed_sheet = True msgs = cog.util.merge_msgs_to_least(msgs) for msg in cog.util.merge_msgs_to_least(msgs): Loading Loading @@ -927,6 +932,10 @@ class Drop(Action): await check_sheet(client=self, scanner_name='hudson_cattle', attr='fort_user', user_cls=FortUser) drop = cogdb.query.fort_add_drop(self.session, system=system, user=self.duser.fort_user, amount=self.args.amount) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='fort', ) if self.args.set: system.set_status(self.args.set) Loading @@ -942,6 +951,7 @@ class Drop(Action): ) scanner = get_scanner("hudson_cattle") await scanner.send_batch(self.payloads) record.flushed_sheet = True self.log.info('DROP %s - Sucessfully dropped %d at %s.', self.duser.display_name, self.args.amount, system.name) Loading Loading @@ -1003,6 +1013,10 @@ class Fort(Action): system = cogdb.query.fort_find_system(self.session, system_name) system.set_status(self.args.set) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='fort', ) self.session.commit() self.payloads += cogdb.scanners.FortScanner.update_system_dict( Loading @@ -1010,6 +1024,7 @@ class Fort(Action): ) scanner = get_scanner("hudson_cattle") await scanner.send_batch(self.payloads) record.flushed_sheet = True return system.display() Loading Loading @@ -1243,6 +1258,10 @@ class Hold(Action): await self.check_sheet_user() holds, response = await self.set_hold() record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='um' if self.args.sheet_src == EUMSheet.main else "snipe" ) self.session.commit() for hold in holds: Loading @@ -1251,6 +1270,7 @@ class Hold(Action): scanner = get_scanner("hudson_undermine" if self.args.sheet_src == EUMSheet.main else "hudson_snipe") await scanner.send_batch(self.payloads) record.flushed_sheet = True await self.bot.send_message(self.msg.channel, response) Loading Loading @@ -1919,6 +1939,10 @@ class UM(Action): system = cogdb.query.um_find_system(self.session, ' '.join(self.args.system), sheet_src=self.args.sheet_src) record = cogdb.query.add_sheet_record( self.session, discord_id=self.msg.author.id, channel_id=self.msg.channel.id, command=self.msg.content, sheet_src='um' if self.args.sheet_src == EUMSheet.main else "snipe" ) if self.args.offset: system.map_offset = self.args.offset if self.args.priority: Loading @@ -1941,6 +1965,7 @@ class UM(Action): self.session.commit() scanner = get_scanner("hudson_undermine" if self.args.sheet_src == EUMSheet.main else "hudson_snipe") await scanner.send_batch(self.payloads) record.flushed_sheet = True response = system.display() Loading
cog/util.py +15 −4 Original line number Diff line number Diff line Loading @@ -182,15 +182,26 @@ class ReprMixin(): class TimestampMixin(): """ Simple mixing that converts updated_at timestamp to a datetime object. Timestamps on object are assumed to be created as UTC timestamps. """ @property def utc_date(self): """Assumes the timestampe is in fact UTC, native datetime with no tz info. """ def created_date(self): """The created at date as a naive datetime object.""" return datetime.datetime.utcfromtimestamp(self.created_at) @property def created_date_tz(self): """The created at date as a timezone aware datetime object.""" return datetime.datetime.fromtimestamp(self.created_at, tz=datetime.timezone.utc) @property def updated_date(self): """The update at date as a naive datetime object.""" return datetime.datetime.utcfromtimestamp(self.updated_at) @property def utc_date_tz(self): """Assumes the timestampe is in fact UTC, datetime object set to UTC timezone.""" def updated_date_tz(self): """The update at date as a timezone aware datetime object.""" return datetime.datetime.fromtimestamp(self.updated_at, tz=datetime.timezone.utc) Loading
cogdb/query.py +46 −1 Original line number Diff line number Diff line Loading @@ -20,7 +20,8 @@ from cogdb.schema import (DiscordUser, FortSystem, FortPrep, FortDrop, FortUser, EFortType, UMSystem, UMUser, UMHold, EUMSheet, EUMType, KOS, AdminPerm, ChannelPerm, RolePerm, TrackSystem, TrackSystemCached, TrackByID, Global, Vote, EVoteType, Consolidation) Global, Vote, EVoteType, Consolidation, ESheetType, SheetRecord) def dump_db(session): # pragma: no cover Loading Loading @@ -1395,3 +1396,47 @@ def route_systems(eddb_session, systems): mapped_originals = {x.name: x for x in systems} _, routed_systems = cogdb.eddb.find_route_closest_hq(eddb_session, [x.name for x in systems]) return [mapped_originals[x.name].display() for x in routed_systems] def add_sheet_record(session, **kwargs): """ Add a permanent record of a change to the sheets and db. The main purpose is historical to be able to record important commands. Args: session: A session onto the db. discord_id: The discord id of the requesting user. channel_id: The channel id where message was sent. command: The text of the message user sent. sheet_src: The sheet being modified, one of: ['fort', 'um', 'snipe'] Returns: The added SheetRecord """ record = SheetRecord(**kwargs) session.add(record) return record def get_user_sheet_records(session, *, discord_id, cycle=None): """ Get sheet records for a particular cycle and user, a way to see what changes user has requested for bot. Args: session: A session onto the db. discord_id: The discord id of the requesting user. cycle: The cycle to pull information for. Default is current cycle. Returns: All SheetRecords matching, empty list if none found. """ if not cycle: cycle = cog.util.current_cycle() return session.query(SheetRecord).\ filter(SheetRecord.discord_id == discord_id, SheetRecord.cycle == cycle).\ order_by(SheetRecord.created_at).\ all()
cogdb/scanners.py +1 −1 Original line number Diff line number Diff line Loading @@ -1075,7 +1075,7 @@ class BGSDemo(FortScanner): ", ".join([x.state.text for x in entry.pending_states]), entry.happiness.text, entry.influence, str(entry.utc_date), str(entry.updated_date), ]] end_row = row + len(values) Loading
cogdb/schema.py +42 −4 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ These allow the bot to store and query the information in sheets that are parsed """ import datetime import enum import time import sqlalchemy as sqla import sqlalchemy.orm as sqla_orm Loading @@ -16,7 +17,7 @@ import cog.exc import cog.tbl import cog.util import cogdb from cog.util import ReprMixin from cog.util import ReprMixin, TimestampMixin LEN_CMD = 25 # Max length of a subclass of cog.actions Loading @@ -24,6 +25,7 @@ LEN_NAME = 100 LEN_REASON = 400 LEN_SHEET_COL = 5 LEN_CARRIER = 7 LEN_COMMAND = 2000 MAX_VOTE_VALUE = 50 EVENT_CARRIER = """ CREATE EVENT IF NOT EXISTS clean_carriers Loading Loading @@ -52,7 +54,7 @@ class DiscordUser(ReprMixin, Base): _repr_keys = ['id', 'display_name', 'pref_name', 'pref_cry'] id = sqla.Column(sqla.BigInteger, primary_key=True) # Discord id display_name = sqla.Column(sqla.String(LEN_NAME)) display_name = sqla.Column(sqla.String(LEN_NAME)) # FIXME: Remove this pref_name = sqla.Column(sqla.String(LEN_NAME), index=True, nullable=False) # pref_name == display_name until change pref_cry = sqla.Column(sqla.String(LEN_NAME), default='') Loading Loading @@ -1245,7 +1247,7 @@ class Consolidation(ReprMixin, Base): Track the consolidation vote changes over time. """ __tablename__ = 'consolidation_tracker' _repr_keys = ['id', 'amount', 'cons_total', 'prep_total', 'updated_at'] _repr_keys = ['id', 'cycle', 'amount', 'cons_total', 'prep_total', 'updated_at'] id = sqla.Column(sqla.BigInteger, primary_key=True) amount = sqla.Column(sqla.Integer, default=0) Loading Loading @@ -1284,6 +1286,42 @@ class Consolidation(ReprMixin, Base): return value class ESheetType(enum.Enum): """ Type of sheet the transaction modified. """ fort = 1 um = 2 snipe = 3 class SheetRecord(ReprMixin, TimestampMixin, Base): """ For every command modifying the local database and sheet, record a transaction. """ __tablename__ = 'history_sheet_transactions' _repr_keys = ['id', 'discord_id', 'channel_id', 'sheet_src', 'cycle', 'command', 'flushed_sheet', 'created_at'] id = sqla.Column(sqla.BigInteger, primary_key=True) discord_id = sqla.Column(sqla.BigInteger, nullable=False) channel_id = sqla.Column(sqla.BigInteger, nullable=False) sheet_src = sqla.Column(sqla.Enum(ESheetType), default=ESheetType.fort) cycle = sqla.Column(sqla.Integer, default=cog.util.current_cycle) command = sqla.Column(sqla.String(LEN_COMMAND), default="") flushed_sheet = sqla.Column(sqla.Boolean, default=False) created_at = sqla.Column(sqla.Integer, default=time.time) # Relationships user = sqla_orm.relationship('DiscordUser', uselist=False, viewonly=True, lazy='joined', primaryjoin='foreign(SheetRecord.discord_id) == DiscordUser.id') def __eq__(self, other): return (isinstance(self, SheetRecord) and isinstance(other, SheetRecord) and self.id == other.id) def __hash__(self): return hash(self.id) def kwargs_um_system(cells, sheet_col, *, sheet_src=EUMSheet.main): """ Return keyword args parsed from cell frame. Loading Loading @@ -1424,7 +1462,7 @@ def empty_tables(session, *, perm=False): """ Drop all tables. """ classes = [FortDrop, UMHold, FortSystem, UMSystem, FortUser, UMUser, KOS, classes = [SheetRecord, FortDrop, UMHold, FortSystem, UMSystem, FortUser, UMUser, KOS, KOS, TrackSystem, TrackSystemCached, TrackByID, AdminPerm, ChannelPerm, RolePerm] if perm: Loading