import json from abc import ABCMeta, abstractmethod from collections import namedtuple from datetime import datetime from dateutil.relativedelta import relativedelta from six import add_metaclass from tzlocal import get_localzone from app import avatar from endpoints.api import format_date from util.morecollections import AttrDict class LogEntry( namedtuple('LogEntry', [ 'metadata_json', 'ip', 'datetime', 'performer_email', 'performer_username', 'performer_robot', 'account_organization', 'account_username', 'account_email', 'account_robot', 'kind_id' ])): """ LogEntry a single log entry. :type metadata_json: string :type ip: string :type datetime: string :type performer_email: int :type performer_username: string :type performer_robot: boolean :type account_organization: boolean :type account_username: string :type account_email: string :type account_robot: boolean :type kind_id: int """ def to_dict(self, kinds, include_namespace): view = { 'kind': kinds[self.kind_id], 'metadata': json.loads(self.metadata_json), 'ip': self.ip, 'datetime': format_date(self.datetime), } if self.performer_username: performer = AttrDict({'username': self.performer_username, 'email': self.performer_email}) performer.robot = None if self.performer_robot: performer.robot = self.performer_robot view['performer'] = { 'kind': 'user', 'name': self.performer_username, 'is_robot': self.performer_robot, 'avatar': avatar.get_data_for_user(performer), } if include_namespace: if self.account_username: account = AttrDict({'username': self.account_username, 'email': self.account_email}) if self.account_organization: view['namespace'] = { 'kind': 'org', 'name': self.account_username, 'avatar': avatar.get_data_for_org(account), } else: account.robot = None if self.account_robot: account.robot = self.account_robot view['namespace'] = { 'kind': 'user', 'name': self.account_username, 'avatar': avatar.get_data_for_user(account), } return view class LogEntryPage( namedtuple('LogEntryPage', ['logs', 'next_page_token'])): """ LogEntryPage represents a single page of logs. :type logs: [LogEntry] :type next_page_token: {any -> any} """ class AggregatedLogEntry( namedtuple('AggregatedLogEntry', ['count', 'kind_id', 'day'])): """ AggregatedLogEntry represents an aggregated view of logs. :type count: int :type kind_id: int :type day: string """ def to_dict(self, kinds, start_time): synthetic_date = datetime(start_time.year, start_time.month, int(self.day), tzinfo=get_localzone()) if synthetic_date.day < start_time.day: synthetic_date = synthetic_date + relativedelta(months=1) view = { 'kind': kinds[self.kind_id], 'count': self.count, 'datetime': format_date(synthetic_date), } return view @add_metaclass(ABCMeta) class LogEntryDataInterface(object): """ Interface that represents all data store interactions required by a Log. """ @abstractmethod def get_logs_query(self, start_time, end_time, performer_name=None, repository_name=None, namespace_name=None, ignore=None, page_token=None): """ Returns a LogEntryPage. """ @abstractmethod def get_log_entry_kinds(self): """ Returns a map of LogEntryKind id -> name and name -> id """ @abstractmethod def repo_exists(self, namespace_name, repository_name): """ Returns whether or not a repo exists. """ @abstractmethod def get_aggregated_logs(self, start_time, end_time, performer_name=None, repository_name=None, namespace_name=None, ignore=None): """ Returns a list of aggregated logs """