Merge pull request #103 from eReuse/feature/endpoint-metrics

Feature/endpoint metrics
This commit is contained in:
cayop 2021-01-14 17:49:13 +01:00 committed by GitHub
commit 0d5e5590e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 171 additions and 6 deletions

View File

@ -15,6 +15,7 @@ ml).
- [addend] #95 adding endpoint for check the hash of one report - [addend] #95 adding endpoint for check the hash of one report
- [addend] #98 adding endpoint for insert a new live - [addend] #98 adding endpoint for insert a new live
- [addend] #98 adding endpoint for get all licences in one query - [addend] #98 adding endpoint for get all licences in one query
- [addend] #102 adding endpoint for download metrics
- [bugfix] #100 fixing bug of scheme live - [bugfix] #100 fixing bug of scheme live
- [bugfix] #101 fixing bug when 2 users have one device and launch one live - [bugfix] #101 fixing bug when 2 users have one device and launch one live

View File

@ -32,8 +32,8 @@ from sqlalchemy.ext.orderinglist import ordering_list
from sqlalchemy.orm import backref, relationship, validates from sqlalchemy.orm import backref, relationship, validates
from sqlalchemy.orm.events import AttributeEvents as Events from sqlalchemy.orm.events import AttributeEvents as Events
from sqlalchemy.util import OrderedSet from sqlalchemy.util import OrderedSet
from teal.db import CASCADE_OWN, INHERIT_COND, IP, POLYMORPHIC_ID, \ from teal.db import (CASCADE_OWN, INHERIT_COND, IP, POLYMORPHIC_ID,
POLYMORPHIC_ON, StrictVersionType, URL, check_lower, check_range POLYMORPHIC_ON, StrictVersionType, URL, check_lower, check_range, ResourceNotFound)
from teal.enums import Country, Currency, Subdivision from teal.enums import Country, Currency, Subdivision
from teal.marshmallow import ValidationError from teal.marshmallow import ValidationError
from teal.resource import url_for_resource from teal.resource import url_for_resource
@ -543,6 +543,26 @@ class Snapshot(JoinedWithOneDeviceMixin, ActionWithOneDevice):
of time it took to complete. of time it took to complete.
""" """
def get_last_lifetimes(self):
"""We get the lifetime and serial_number of the first disk"""
hdds = []
components = [c for c in self.components]
components.sort(key=lambda x: x.created)
for hd in components:
data = {'serial_number': None, 'lifetime': 0}
if not isinstance(hd, DataStorage):
continue
data['serial_number'] = hd.serial_number
for act in hd.actions:
if not act.type == "TestDataStorage":
continue
data['lifetime'] = act.lifetime.total_seconds()
break
hdds.append(data)
return hdds
def __str__(self) -> str: def __str__(self) -> str:
return '{}. {} version {}.'.format(self.severity, self.software, self.version) return '{}. {} version {}.'.format(self.severity, self.software, self.version)

View File

@ -124,7 +124,9 @@ class LiveView(View):
"""We get the liftime and serial_number of the disk""" """We get the liftime and serial_number of the disk"""
usage_time_hdd = None usage_time_hdd = None
serial_number = None serial_number = None
for hd in snapshot['components']: components = [c for c in snapshot['components']]
components.sort(key=lambda x: x.created)
for hd in components:
if not isinstance(hd, DataStorage): if not isinstance(hd, DataStorage):
continue continue

View File

@ -1,4 +1,5 @@
import pathlib import pathlib
import copy
from contextlib import suppress from contextlib import suppress
from fractions import Fraction from fractions import Fraction
from itertools import chain from itertools import chain
@ -317,6 +318,57 @@ class Device(Thing):
def _warning_actions(self, actions): def _warning_actions(self, actions):
return sorted(ev for ev in actions if ev.severity >= Severity.Warning) return sorted(ev for ev in actions if ev.severity >= Severity.Warning)
def get_metrics(self):
"""
This method get a list of values for calculate a metrics from a spreadsheet
"""
actions = copy.copy(self.actions)
actions.sort(key=lambda x: x.created)
allocates = []
for act in actions:
if act.type == 'Snapshot':
snapshot = act
lifestimes = snapshot.get_last_lifetimes()
lifetime = 0
if lifestimes:
lifetime = lifestimes[0]['lifetime']
if act.type == 'Allocate':
allo = {'type': 'Allocate',
'userCode': act.final_user_code,
'numUsers': act.end_users,
'hid': self.hid,
'liveCreate': 0,
'liveLifetime': 0,
'start': act.start_time,
'allocateLifetime': lifetime}
allocates.append(allo)
if act.type == 'Live':
allocate = copy.copy(allo)
lifetime = act.usage_time_hdd.total_seconds()
allocate['type'] = 'Live'
allocate['liveCreate'] = act.created
for hd in lifestimes:
if hd['serial_number'] == act.serial_number:
lifetime = hd['lifetime']
break
allocate['liveLifetime'] = lifetime
allocates.append(allocate)
if act.type == 'Deallocate':
deallo = {'type': 'Deallocate',
'userCode': '',
'numUsers': '',
'hid': self.hid,
'liveCreate': 0,
'liveLifetime': lifetime,
'start': act.start_time,
'allocateLifetime': 0}
allocates.append(deallo)
return allocates
def __lt__(self, other): def __lt__(self, other):
return self.id < other.id return self.id < other.id

View File

@ -5,6 +5,7 @@ from flask import url_for
from ereuse_devicehub.resources.enums import Severity from ereuse_devicehub.resources.enums import Severity
from ereuse_devicehub.resources.device import models as d, states from ereuse_devicehub.resources.device import models as d, states
from ereuse_devicehub.resources.action import models as da
from ereuse_devicehub.resources.action.models import (BenchmarkDataStorage, RateComputer, from ereuse_devicehub.resources.action.models import (BenchmarkDataStorage, RateComputer,
TestDataStorage) TestDataStorage)
@ -360,3 +361,18 @@ def get_action(component, action):
""" Filter one action from a component or return None """ """ Filter one action from a component or return None """
result = [a for a in component.actions if a.type == action] result = [a for a in component.actions if a.type == action]
return result[-1] if result else None return result[-1] if result else None
class ActionRow(OrderedDict):
def __init__(self, allocate):
super().__init__()
# General information about allocates, deallocate and lives
self['Hid'] = allocate['hid']
self['Start'] = allocate['start']
self['UserCode'] = allocate['userCode']
self['NumUsers'] = allocate['numUsers']
self['AllocateLifetime'] = allocate['allocateLifetime']
self['Type'] = allocate['type']
self['LiveCreate'] = allocate['liveCreate']
self['LiveLifetime'] = allocate['liveLifetime']

View File

@ -20,7 +20,7 @@ from ereuse_devicehub.db import db
from ereuse_devicehub.resources.action import models as evs 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.device.views import DeviceView from ereuse_devicehub.resources.device.views import DeviceView
from ereuse_devicehub.resources.documents.device_row import DeviceRow, StockRow from ereuse_devicehub.resources.documents.device_row import DeviceRow, StockRow, ActionRow
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 from ereuse_devicehub.resources.hash_reports import insert_hash, ReportHash
@ -133,6 +133,32 @@ class DevicesDocumentView(DeviceView):
return output return output
class ActionsDocumentView(DeviceView):
@cache(datetime.timedelta(minutes=1))
def find(self, args: dict):
query = (x for x in self.query(args) if x.owner_id == g.user.id)
return self.generate_post_csv(query)
def generate_post_csv(self, query):
"""Get device query and put information in csv format."""
data = StringIO()
cw = csv.writer(data, delimiter=';', lineterminator="\n", quotechar='"')
first = True
for device in query:
for allocate in device.get_metrics():
d = ActionRow(allocate)
if first:
cw.writerow(d.keys())
first = False
cw.writerow(d.values())
bfile = data.getvalue().encode('utf-8')
output = make_response(bfile)
insert_hash(bfile)
output.headers['Content-Disposition'] = 'attachment; filename=actions_export.csv'
output.headers['Content-type'] = 'text/csv'
return output
class LotsDocumentView(LotView): class LotsDocumentView(LotView):
def find(self, args: dict): def find(self, args: dict):
query = (x for x in self.query(args) if x.owner_id == g.user.id) query = (x for x in self.query(args) if x.owner_id == g.user.id)
@ -256,3 +282,9 @@ class DocumentDef(Resource):
check_view = CheckView.as_view('CheckView', definition=self, auth=app.auth) check_view = CheckView.as_view('CheckView', definition=self, auth=app.auth)
self.add_url_rule('/check/', defaults={}, view_func=check_view, methods=get) self.add_url_rule('/check/', defaults={}, view_func=check_view, methods=get)
actions_view = ActionsDocumentView.as_view('ActionsDocumentView',
definition=self,
auth=app.auth)
actions_view = app.auth.requires_auth(actions_view)
self.add_url_rule('/actions/', defaults=d, view_func=actions_view, methods=get)

View File

@ -49,6 +49,7 @@ def test_api_docs(client: Client):
'/displays/{dev1_id}/merge/{dev2_id}', '/displays/{dev1_id}/merge/{dev2_id}',
'/diy-and-gardenings/{dev1_id}/merge/{dev2_id}', '/diy-and-gardenings/{dev1_id}/merge/{dev2_id}',
'/documents/devices/', '/documents/devices/',
'/documents/actions/',
'/documents/erasures/', '/documents/erasures/',
'/documents/lots/', '/documents/lots/',
'/documents/static/{filename}', '/documents/static/{filename}',

View File

@ -11,7 +11,7 @@ from ereuse_utils.test import ANY
from ereuse_devicehub.client import Client, UserClient from ereuse_devicehub.client import Client, UserClient
from ereuse_devicehub.devicehub import Devicehub from ereuse_devicehub.devicehub import Devicehub
from ereuse_devicehub.resources.action.models import Snapshot from ereuse_devicehub.resources.action.models import Snapshot, Allocate, Live
from ereuse_devicehub.resources.documents import documents from ereuse_devicehub.resources.documents import documents
from ereuse_devicehub.resources.device import models as d from ereuse_devicehub.resources.device import models as d
from ereuse_devicehub.resources.lot.models import Lot from ereuse_devicehub.resources.lot.models import Lot
@ -85,7 +85,7 @@ def test_erasure_certificate_wrong_id(client: Client):
@pytest.mark.mvp @pytest.mark.mvp
def test_export_csv_permitions(user: UserClient, user2: UserClient, client: Client): def test_export_csv_permitions(user: UserClient, user2: UserClient, client: Client):
"""Test export device information in a csv file with others users.""" """test export device information in a csv file with others users."""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot) snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)
csv_user, _ = user.get(res=documents.DocumentDef.t, csv_user, _ = user.get(res=documents.DocumentDef.t,
item='devices/', item='devices/',
@ -108,6 +108,47 @@ def test_export_csv_permitions(user: UserClient, user2: UserClient, client: Clie
@pytest.mark.mvp @pytest.mark.mvp
def test_export_csv_actions(user: UserClient, user2: UserClient, client: Client):
"""Test export device information in a csv file with others users."""
acer = file('acer.happy.battery.snapshot')
snapshot, _ = user.post(acer, res=Snapshot)
device_id = snapshot['device']['id']
post_request = {"transaction": "ccc", "name": "John", "endUsers": 1,
"devices": [device_id], "description": "aaa",
"finalUserCode": "abcdefjhi",
"startTime": "2020-11-01T02:00:00+00:00",
"endTime": "2020-12-01T02:00:00+00:00"
}
user.post(res=Allocate, data=post_request)
hdd = [c for c in acer['components'] if c['type'] == 'HardDrive'][0]
hdd_action = [a for a in hdd['actions'] if a['type'] == 'TestDataStorage'][0]
hdd_action['lifetime'] += 1000
acer.pop('elapsed')
acer['licence_version'] = '1.0.0'
snapshot, _ = client.post(acer, res=Live)
csv_user, _ = user.get(res=documents.DocumentDef.t,
item='actions/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
csv_user2, _ = user2.get(res=documents.DocumentDef.t,
item='actions/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})])
_, res = client.get(res=documents.DocumentDef.t,
item='actions/',
accept='text/csv',
query=[('filter', {'type': ['Computer']})], status=401)
assert res.status_code == 401
assert len(csv_user) > 0
assert len(csv_user2) == 0
@pytest.mark.mvp
def test_export_basic_snapshot(user: UserClient): def test_export_basic_snapshot(user: UserClient):
"""Test export device information in a csv file.""" """Test export device information in a csv file."""
snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot) snapshot, _ = user.post(file('basic.snapshot'), res=Snapshot)