resolve confict
This commit is contained in:
commit
6bb608b1a6
|
@ -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
|
||||||
|
|
|
@ -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', '')
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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/',
|
||||||
|
|
|
@ -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() == '""'
|
||||||
|
|
Reference in New Issue