From 0099d98ed2ba173e8a7cebacfa56f170ea390ad7 Mon Sep 17 00:00:00 2001 From: Megan Henning <meganhenning@flywheel.io> Date: Sun, 27 Aug 2017 18:35:29 -0500 Subject: [PATCH] Minor adjustments to access log csv --- api/handlers/reporthandler.py | 98 ++++++++++++++++++++--------------- api/web/base.py | 2 - 2 files changed, 55 insertions(+), 45 deletions(-) diff --git a/api/handlers/reporthandler.py b/api/handlers/reporthandler.py index aa0e44ba..f8946006 100644 --- a/api/handlers/reporthandler.py +++ b/api/handlers/reporthandler.py @@ -1,11 +1,11 @@ +import bson import copy import unicodecsv as csv import datetime -import os - -import bson import dateutil +import os import pymongo +import pytz from .. import config from .. import tempdir as tempfile @@ -19,29 +19,29 @@ EIGHTEEN_YEARS_IN_SEC = 18 * 365.25 * 24 * 60 * 60 BYTES_IN_MEGABYTE = float(1<<20) ACCESS_LOG_FIELDS = [ "_id", + "timestamp", "access_type", - "context.acquisition.id", - "context.acquisition.label", - "context.analysis.id", - "context.analysis.label", - "context.collection.id", - "context.collection.label", + "origin.id", + "origin.method", + "origin.name", + "origin.type", "context.group.id", "context.group.label", "context.project.id", "context.project.label", - "context.session.id", - "context.session.label", "context.subject.id", "context.subject.label", + "context.session.id", + "context.session.label", + "context.acquisition.id", + "context.acquisition.label", + "context.analysis.id", + "context.analysis.label", + "context.collection.id", + "context.collection.label", "context.ticket_id", - "origin.id", - "origin.method", - "origin.name", - "origin.type", "request_method", - "request_path", - "timestamp" + "request_path" ] class APIReportException(Exception): @@ -73,24 +73,16 @@ class ReportHandler(base.RequestHandler): raise NotImplementedError('Report type {} is not supported'.format(report_type)) if self.superuser_request or report.user_can_generate(self.uid): - # If csv is true create a temp file to respond with - if report_type == 'accesslog' and self.request.params.get('csv') == 'true': + if self.is_true('csv'): tempdir = tempfile.TemporaryDirectory(prefix='.tmp', dir=config.get_item('persistent', 'data_path')) - csv_file = open(os.path.join(tempdir.name, 'accesslog.csv'), 'w+') - writer = csv.DictWriter(csv_file, ACCESS_LOG_FIELDS) - writer.writeheader() - try: - for doc in report.build(): - writer.writerow(doc) - - except APIReportException as e: - self.abort(404, str(e)) - # Need to close and reopen file to flush buffer into file - csv_file.close() - self.response.app_iter = open(os.path.join(tempdir.name, 'accesslog.csv'), 'r') + filepath = os.path.join(tempdir.name, report.csv_filename) + + report.build_csv(filepath) + + self.response.app_iter = open(filepath, 'r') self.response.headers['Content-Type'] = 'text/csv' - self.response.headers['Content-Disposition'] = 'attachment; filename="accesslog.csv"' + self.response.headers['Content-Disposition'] = 'attachment; filename="{}"'.format(report.csv_filename) else: return report.build() else: @@ -119,6 +111,13 @@ class Report(object): """ raise NotImplementedError() + def build_csv(self): + """ + Build and return a csv file of the report + """ + raise APIReportParamsException('This report does not support csv file format.') + + @staticmethod def _get_result_list(output): """ @@ -481,6 +480,9 @@ class AccessLogReport(Report): - information about the session/project/group in which the action took place """ + # What to name csvs generated from this report + csv_filename = 'accesslog.csv' + def __init__(self, params): """ Initialize an Access Log Report @@ -503,7 +505,6 @@ class AccessLogReport(Report): limit= params.get('limit', 100) subject = params.get('subject', None) access_types = params.getall('access_type') - csv_bool = (params.get('csv') == 'true') if start_date: start_date = dateutil.parser.parse(start_date) @@ -531,7 +532,6 @@ class AccessLogReport(Report): self.limit = limit self.subject = subject self.access_types = access_types - self.csv_bool = csv_bool def user_can_generate(self, uid): """ @@ -541,21 +541,20 @@ class AccessLogReport(Report): return True return False - def flatten(self, json_obj, flat, prefix = ""): + def flatten(self, json_obj, flat=None, prefix = ""): """ flattens a document to not have nested objects """ + if flat is None: + flat = {} for field in json_obj.keys(): if isinstance(json_obj[field], dict): - flat = self.flatten(json_obj[field], flat, prefix = prefix + field + ".") + flat = self.flatten(json_obj[field], flat=flat, prefix = prefix + field + ".") else: flat[prefix + field] = json_obj[field] return flat - def make_csv_ready(self, cursor): - return [self.flatten(json_obj, {}) for json_obj in cursor] - def build(self): query = {} @@ -572,11 +571,24 @@ class AccessLogReport(Report): if self.access_types: query['access_type'] = {'$in': self.access_types} - cursor = config.log_db.access_log.find(query).limit(self.limit).sort('timestamp', pymongo.DESCENDING).batch_size(1000) - if self.csv_bool: - return self.make_csv_ready(cursor) + return config.log_db.access_log.find(query).limit(self.limit).sort('timestamp', pymongo.DESCENDING).batch_size(1000) + + def build_csv(self, filepath): + csv_file = open(filepath, 'w+') + writer = csv.DictWriter(csv_file, ACCESS_LOG_FIELDS) + writer.writeheader() + + for doc in self.build(): + + # Format timestamp as ISO UTC + doc['timestamp'] = pytz.timezone('UTC').localize(doc['timestamp']).isoformat() + + writer.writerow(self.flatten(doc)) + + # Need to close and reopen file to flush buffer into file + csv_file.close() + - return cursor class UsageReport(Report): """ diff --git a/api/web/base.py b/api/web/base.py index 41e13db1..f2fbb144 100644 --- a/api/web/base.py +++ b/api/web/base.py @@ -374,8 +374,6 @@ class RequestHandler(webapp2.RequestHandler): for k,v in tree.iteritems(): context[k] = {'id': str(v['_id']), 'label': v.get('label')} - if k == 'group': - context[k]['label'] = v.get('name') if k == 'subject': context[k]['label'] = v.get('code') log_map['context'] = context -- GitLab