resolve confict

This commit is contained in:
Cayo Puigdefabregas 2021-03-04 10:51:20 +01:00
commit 6bb608b1a6
7 changed files with 168 additions and 4 deletions

View file

@ -12,6 +12,7 @@ ml).
[1.0.5-beta] [1.0.5-beta]
## [1.0.5-beta] ## [1.0.5-beta]
- [addend] #124 adding endpoint for extract the internal stats of use
## [1.0.4-beta] ## [1.0.4-beta]
- [addend] #95 adding endpoint for check the hash of one report - [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_VERSION = StrictVersion('1.0')
PRICE_CURRENCY = Currency.EUR PRICE_CURRENCY = Currency.EUR
"""Official versions.""" """Official versions."""
"""Admin email"""
EMAIL_ADMIN = config('EMAIL_ADMIN', '')

View file

@ -379,3 +379,84 @@ class ActionRow(OrderedDict):
self['Type'] = allocate['type'] self['Type'] = allocate['type']
self['LiveCreate'] = allocate['liveCreate'] self['LiveCreate'] = allocate['liveCreate']
self['UsageTimeHdd'] = allocate['usageTimeHdd'] 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 not _ac.type == 'Snapshot':
continue
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 collections import OrderedDict
from io import StringIO from io import StringIO
from typing import Callable, Iterable, Tuple from typing import Callable, Iterable, Tuple
from decouple import config
import boltons import boltons
import flask import flask
@ -12,6 +13,7 @@ import flask_weasyprint
import teal.marshmallow import teal.marshmallow
from boltons import urlutils from boltons import urlutils
from flask import make_response, g, request from flask import make_response, g, request
from flask import current_app as app
from flask.json import jsonify from flask.json import jsonify
from teal.cache import cache from teal.cache import cache
from teal.resource import Resource, View 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.device import models as devs
from ereuse_devicehub.resources.deliverynote.models import Deliverynote from ereuse_devicehub.resources.deliverynote.models import Deliverynote
from ereuse_devicehub.resources.device.views import DeviceView 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 import LotView
from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.lot.models import Lot
from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash, verify_hash from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash, verify_hash
@ -215,7 +218,7 @@ class StockDocumentView(DeviceView):
def generate_post_csv(self, query): def generate_post_csv(self, query):
"""Get device query and put information in csv format.""" """Get device query and put information in csv format."""
data = StringIO() data = StringIO()
cw = csv.writer(data) cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
first = True first = True
for device in query: for device in query:
d = StockRow(device) d = StockRow(device)
@ -277,6 +280,43 @@ class StampsView(View):
result=result) result=result)
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): class DocumentDef(Resource):
__type__ = 'Document' __type__ = 'Document'
SCHEMA = None SCHEMA = None
@ -332,8 +372,14 @@ class DocumentDef(Resource):
stamps_view = StampsView.as_view('StampsView', definition=self, auth=app.auth) stamps_view = StampsView.as_view('StampsView', definition=self, auth=app.auth)
self.add_url_rule('/stamps/', defaults={}, view_func=stamps_view, methods={'GET', 'POST'}) self.add_url_rule('/stamps/', defaults={}, view_func=stamps_view, methods={'GET', 'POST'})
actions_view = ActionsDocumentView.as_view('ActionsDocumentView', internalstats_view = InternalStatsView.as_view(
definition=self, '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) auth=app.auth)
actions_view = app.auth.requires_auth(actions_view) actions_view = app.auth.requires_auth(actions_view)
self.add_url_rule('/actions/', defaults=d, view_func=actions_view, methods=get) self.add_url_rule('/actions/', defaults=d, view_func=actions_view, methods=get)

View file

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

View file

@ -40,6 +40,7 @@ def test_api_docs(client: Client):
'/documents/erasures/', '/documents/erasures/',
'/documents/devices/', '/documents/devices/',
'/documents/stamps/', '/documents/stamps/',
'/documents/internalstats/',
'/documents/stock/', '/documents/stock/',
'/documents/check/', '/documents/check/',
'/documents/lots/', '/documents/lots/',

View file

@ -415,6 +415,8 @@ def test_report_devices_stock_control(user: UserClient, user2: UserClient):
assert user.user['id'] != user2.user['id'] assert user.user['id'] != user2.user['id']
assert len(export_csv) == 2 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), \ assert isinstance(datetime.strptime(export_csv[1][5], '%c'), datetime), \
'Register in field is not a datetime' 'Register in field is not a datetime'
@ -614,3 +616,32 @@ def test_verify_stamp_erasure_certificate(user: UserClient, client: Client):
'example.csv')]}, 'example.csv')]},
status=200) status=200)
assert "alert alert-info" in response assert "alert alert-info" in response
@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() == '""'