Merge pull request #124 from eReuse/feature/#123-internal-stats

basic endpoint for internal stats
This commit is contained in:
cayop 2021-02-23 12:34:35 +01:00 committed by GitHub
commit 684e90ae56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 165 additions and 5 deletions

View File

@ -12,6 +12,7 @@ ml).
[1.0.5-beta]
## [1.0.5-beta]
- [addend] #124 adding endpoint for extract the internal stats of use
## [1.0.4-beta]
- [addend] #95 adding endpoint for check the hash of one report

View File

@ -65,3 +65,6 @@ class DevicehubConfig(Config):
PRICE_VERSION = StrictVersion('1.0')
PRICE_CURRENCY = Currency.EUR
"""Official versions."""
"""Admin email"""
EMAIL_ADMIN = config('EMAIL_ADMIN', '')

View File

@ -379,3 +379,81 @@ class ActionRow(OrderedDict):
self['Type'] = allocate['type']
self['LiveCreate'] = allocate['liveCreate']
self['UsageTimeHdd'] = allocate['usageTimeHdd']
class InternalStatsRow(OrderedDict):
def __init__(self, user, create, actions):
super().__init__()
# General information about all internal stats
# user, quart, month, year:
# Snapshot (Registers)
# Snapshots (Update)
# Snapshots (All)
# Allocate
# Deallocate
# Live
self.actions = actions
year, month = create.split('-')
self['User'] = user
self['Year'] = year
self['Quarter'] = self.quarter(month)
self['Month'] = month
self['Snapshot (Registers)'] = 0
self['Snapshot (Update)'] = 0
self['Snapshot (All)'] = 0
self['Allocates'] = 0
self['Deallocates'] = 0
self['Lives'] = 0
self.count_actions()
def count_actions(self):
for ac in self.actions:
self.is_snapshot(
self.is_deallocate(
self.is_live(
self.is_allocate(ac)
)
)
)
def is_allocate(self, ac):
if ac.type == 'Allocate':
self['Allocates'] += 1
return ac
def is_live(self, ac):
if ac.type == 'Live':
self['Lives'] += 1
return ac
def is_deallocate(self, ac):
if ac.type == 'Deallocate':
self['Deallocates'] += 1
return ac
def is_snapshot(self, ac):
if not ac.type == 'Snapshot':
return
self['Snapshot (All)'] += 1
canary = False
for _ac in ac.device.actions:
if _ac.created < ac.created:
canary = True
break
if canary:
self['Snapshot (Update)'] += 1
else:
self['Snapshot (Registers)'] += 1
def quarter(self, month):
q = {1: 'Q1', 2: 'Q1', 3: 'Q1',
4: 'Q2', 5: 'Q2', 6: 'Q2',
7: 'Q3', 8: 'Q3', 9: 'Q3',
10: 'Q4', 11: 'Q4', 12: 'Q4',
}
return q[int(month)]

View File

@ -5,6 +5,7 @@ import uuid
from collections import OrderedDict
from io import StringIO
from typing import Callable, Iterable, Tuple
from decouple import config
import boltons
import flask
@ -12,6 +13,7 @@ import flask_weasyprint
import teal.marshmallow
from boltons import urlutils
from flask import make_response, g, request
from flask import current_app as app
from flask.json import jsonify
from teal.cache import cache
from teal.resource import Resource, View
@ -21,7 +23,8 @@ from ereuse_devicehub.resources.action import models as evs
from ereuse_devicehub.resources.device import models as devs
from ereuse_devicehub.resources.deliverynote.models import Deliverynote
from ereuse_devicehub.resources.device.views import DeviceView
from ereuse_devicehub.resources.documents.device_row import DeviceRow, StockRow, ActionRow
from ereuse_devicehub.resources.documents.device_row import (DeviceRow, StockRow, ActionRow,
InternalStatsRow)
from ereuse_devicehub.resources.lot import LotView
from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash
@ -212,7 +215,7 @@ class StockDocumentView(DeviceView):
def generate_post_csv(self, query):
"""Get device query and put information in csv format."""
data = StringIO()
cw = csv.writer(data)
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
first = True
for device in query:
d = StockRow(device)
@ -252,6 +255,43 @@ class StampsView(View):
return flask.render_template('documents/stamp.html', rq_url=url_path)
class InternalStatsView(DeviceView):
@cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
if not g.user.email == app.config['EMAIL_ADMIN']:
return jsonify('')
query = evs.Action.query.filter(
evs.Action.type.in_(('Snapshot', 'Live', 'Allocate', 'Deallocate')))
return self.generate_post_csv(query)
def generate_post_csv(self, query):
d = {}
for ac in query:
create = '{}-{}'.format(ac.created.year, ac.created.month)
user = ac.author.email
if not user in d:
d[user] = {}
if not create in d[user]:
d[user][create] = []
d[user][create].append(ac)
data = StringIO()
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
cw.writerow(InternalStatsRow('', "2000-1", []).keys())
for user, createds in d.items():
for create, actions in createds.items():
cw.writerow(InternalStatsRow(user, create, actions).values())
bfile = data.getvalue().encode('utf-8')
output = make_response(bfile)
insert_hash(bfile)
output.headers['Content-Disposition'] = 'attachment; filename=internal-stats.csv'
output.headers['Content-type'] = 'text/csv'
return output
class DocumentDef(Resource):
__type__ = 'Document'
SCHEMA = None
@ -307,6 +347,12 @@ class DocumentDef(Resource):
stamps_view = StampsView.as_view('StampsView', definition=self, auth=app.auth)
self.add_url_rule('/stamps/', defaults={}, view_func=stamps_view, methods=get)
internalstats_view = InternalStatsView.as_view(
'InternalStatsView', definition=self, auth=app.auth)
internalstats_view = app.auth.requires_auth(internalstats_view)
self.add_url_rule('/internalstats/', defaults=d, view_func=internalstats_view,
methods=get)
actions_view = ActionsDocumentView.as_view('ActionsDocumentView',
definition=self,
auth=app.auth)

View File

@ -32,6 +32,7 @@ class TestConfig(DevicehubConfig):
SERVER_NAME = 'localhost'
TMP_SNAPSHOTS = '/tmp/snapshots'
TMP_LIVES = '/tmp/lives'
EMAIL_ADMIN = 'foo@foo.com'
@pytest.fixture(scope='session')

View File

@ -27,7 +27,6 @@ def test_dependencies():
def test_api_docs(client: Client):
"""Tests /apidocs correct initialization."""
docs, _ = client.get('/apidocs')
# import pdb; pdb.set_trace()
assert set(docs['paths'].keys()) == {
'/actions/',
'/apidocs',
@ -41,6 +40,7 @@ def test_api_docs(client: Client):
'/documents/erasures/',
'/documents/devices/',
'/documents/stamps/',
'/documents/internalstats/',
'/documents/stock/',
'/documents/check/',
'/documents/lots/',

View File

@ -414,6 +414,8 @@ def test_report_devices_stock_control(user: UserClient, user2: UserClient):
assert user.user['id'] != user2.user['id']
assert len(export_csv) == 2
export_csv[0] = export_csv[0][0].split(';')
export_csv[1] = export_csv[1][0].split(';')
assert isinstance(datetime.strptime(export_csv[1][5], '%c'), datetime), \
'Register in field is not a datetime'
@ -459,3 +461,32 @@ def test_get_document_lots(user: UserClient, user2: UserClient):
assert export_csv[1][3] == 'comments,lot1,testcomment-lot1,' or 'comments,lot2,testcomment-lot2,'
assert export2_csv[1][1] == 'Lot3-User2'
assert export2_csv[1][3] == 'comments,lot3,testcomment-lot3,'
@pytest.mark.mvp
def test_get_document_internal_stats(user: UserClient, user2: UserClient):
"""Tests for get teh internal stats."""
# csv_str, _ = user.get(res=documents.DocumentDef.t,
# item='internalstats/')
csv_str, _ = user.get(res=documents.DocumentDef.t,
item='internalstats/',
accept='text/csv',
query=[])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
assert len(export_csv) == 1
csv_str, _ = user2.get(res=documents.DocumentDef.t,
item='internalstats/',
accept='text/csv',
query=[])
f = StringIO(csv_str)
obj_csv = csv.reader(f, f)
export_csv = list(obj_csv)
assert csv_str.strip() == '""'