Commit 066ca0ba authored by Jeremy Pallats's avatar Jeremy Pallats 💬
Browse files

Add SheetRecords to track all commands modifying sheets.

- Records will be permanent and per cycle.
- Modify TimestampMixin to better handle created_at and updated_at
  attributes that may exist.
parent 852d7c12
Loading
Loading
Loading
Loading
Loading
+25 −0
Original line number Diff line number Diff line
@@ -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):
@@ -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)
@@ -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)

@@ -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(
@@ -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()

@@ -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:
@@ -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)

@@ -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:
@@ -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()

+15 −4
Original line number Diff line number Diff line
@@ -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)


+46 −1
Original line number Diff line number Diff line
@@ -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
@@ -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()
+1 −1
Original line number Diff line number Diff line
@@ -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)

+42 −4
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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='')

@@ -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)
@@ -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.
@@ -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