Merge pull request #178 from eReuse/feature/new-metrix

Feature/new metrix
This commit is contained in:
cayop 2021-11-08 18:00:12 +01:00 committed by GitHub
commit ec0829faf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 195 additions and 37 deletions

View File

@ -0,0 +1,69 @@
"""
change action_device
Revision ID: 1bb2b5e0fae7
Revises: a0978ac6cf4a
Create Date: 2021-11-04 10:32:49.116399
"""
import sqlalchemy as sa
from alembic import context
from alembic import op
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision = '1bb2b5e0fae7'
down_revision = 'a0978ac6cf4a'
branch_labels = None
depends_on = None
def get_inv():
INV = context.get_x_argument(as_dictionary=True).get('inventory')
if not INV:
raise ValueError("Inventory value is not specified")
return INV
def upgrade_data():
con = op.get_bind()
values = f"action_id, {get_inv()}.action.created"
table = f"{get_inv()}.action_device"
joins = f"inner join {get_inv()}.action"
on = f"on {get_inv()}.action_device.action_id = {get_inv()}.action.id"
sql = f"select {values} from {table} {joins} {on}"
actions_devs = con.execute(sql)
for a in actions_devs:
action_id = a.action_id
created = a.created
sql = f"update {get_inv()}.action_device set created='{created}' where action_id='{action_id}';"
con.execute(sql)
def upgrade():
op.add_column('action_device',
sa.Column('created', sa.TIMESTAMP(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'),
nullable=False, comment='When Devicehub created this.'),
schema=f'{get_inv()}')
op.add_column('action_status',
sa.Column('trade_id', postgresql.UUID(as_uuid=True), nullable=True),
schema=f'{get_inv()}')
op.create_foreign_key("fk_action_status_trade",
"action_status", "trade",
["trade_id"], ["id"],
ondelete="SET NULL",
source_schema=f'{get_inv()}',
referent_schema=f'{get_inv()}')
upgrade_data()
def downgrade():
op.drop_constraint("fk_action_status_trade", "action_status", type_="foreignkey", schema=f'{get_inv()}')
op.drop_column('action_device', 'created', schema=f'{get_inv()}')
op.drop_column('action_status', 'trade_id', schema=f'{get_inv()}')

View File

@ -304,6 +304,23 @@ class ActionDevice(db.Model):
device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True) device_id = Column(BigInteger, ForeignKey(Device.id), primary_key=True)
action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDevices.id), action_id = Column(UUID(as_uuid=True), ForeignKey(ActionWithMultipleDevices.id),
primary_key=True) primary_key=True)
device = relationship(Device,
backref=backref('actions_device',
lazy=True),
primaryjoin=Device.id == device_id)
action = relationship(Action,
backref=backref('actions_device',
lazy=True),
primaryjoin=Action.id == action_id)
created = db.Column(db.TIMESTAMP(timezone=True),
nullable=False,
index=True,
server_default=db.text('CURRENT_TIMESTAMP'))
created.comment = """When Devicehub created this."""
def __init__(self, **kwargs) -> None:
self.created = kwargs.get('created', datetime.now(timezone.utc))
super().__init__(**kwargs)
class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices): class ActionWithMultipleTradeDocuments(ActionWithMultipleDevices):
@ -1360,6 +1377,16 @@ class ActionStatus(JoinedTableMixin, ActionWithMultipleTradeDocuments):
default=lambda: g.user.id) default=lambda: g.user.id)
rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id) rol_user = db.relationship(User, primaryjoin=rol_user_id == User.id)
rol_user_comment = """The user that .""" rol_user_comment = """The user that ."""
trade_id = db.Column(UUID(as_uuid=True),
db.ForeignKey('trade.id'),
nullable=True)
trade = db.relationship('Trade',
backref=backref('status_changes',
uselist=True,
lazy=True,
order_by=lambda: Action.end_time,
collection_class=list),
primaryjoin='ActionStatus.trade_id == Trade.id')
class Recycling(ActionStatus): class Recycling(ActionStatus):

View File

@ -439,7 +439,7 @@ class ActionStatus(Action):
@pre_load @pre_load
def put_devices(self, data: dict): def put_devices(self, data: dict):
if not 'devices' in data.keys(): if 'devices' not in data.keys():
data['devices'] = [] data['devices'] = []
@post_load @post_load
@ -447,9 +447,16 @@ class ActionStatus(Action):
for dev in data['devices']: for dev in data['devices']:
if dev.trading in [None, 'Revoke', 'ConfirmRevoke']: if dev.trading in [None, 'Revoke', 'ConfirmRevoke']:
return data return data
trade = [ac for ac in dev.actions if ac.t == 'Trade'][-1]
trades = [ac for ac in dev.actions if ac.t == 'Trade']
if not trades:
return data
trade = trades[-1]
if trade.user_to != g.user: if trade.user_to != g.user:
data['rol_user'] = trade.user_to data['rol_user'] = trade.user_to
data['trade'] = trade
class Recycling(ActionStatus): class Recycling(ActionStatus):

View File

@ -5,16 +5,17 @@ class MetricsMix:
"""we want get the data metrics of one device""" """we want get the data metrics of one device"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.actions.sort(key=lambda x: x.created) # self.actions.sort(key=lambda x: x.created)
self.rows = [] self.rows = []
self.lifetime = 0 self.lifetime = 0
self.last_trade = None self.last_trade = None
self.action_create_by = 'Receiver' self.action_create_by = 'Receiver'
self.status_receiver = 'Use' self.status_receiver = ''
self.status_supplier = '' self.status_supplier = ''
self.act = None self.act = None
self.end_users = 0 self.end_users = 0
self.final_user_code = '' self.final_user_code = ''
self.trades = {}
def get_template_row(self): def get_template_row(self):
""" """
@ -63,27 +64,34 @@ class Metrics(MetricsMix):
""" """
Mark the status of one device. Mark the status of one device.
If exist one trade before this action, then modify the trade action If exist one trade before this action, then modify the trade action
else, create one row new. else, create one new row.
""" """
self.status_receiver = self.act.type if self.act.trade not in self.trades:
self.status_supplier = '' # If not exist one trade, the status is of the Receive
if self.act.author != self.act.rol_user: self.action_create_by = 'Receiver'
# It is neccesary exist one trade action before self.status_receiver = self.act.type
self.last_trade['status_supplier'] = self.act.type self.status_supplier = ''
self.last_trade['status_supplier_created'] = self.act.created row = self.get_template_row()
row['status_supplier_created'] = ''
row['status_receiver_created'] = self.act.created
self.rows.append(row)
return return
self.action_create_by = 'Receiver' trade = self.trades[self.act.trade]
if self.last_trade:
# if exist one trade action before if trade['trade_supplier'] == self.act.author.email:
self.last_trade['status_receiver'] = self.act.type trade['status_supplier'] = self.act.type
self.last_trade['status_receiver_created'] = self.act.created trade['status_supplier_created'] = self.act.created
return return
# If not exist any trade action for this device if trade['trade_receiver'] == self.act.author.email:
row = self.get_template_row() trade['status_receiver'] = self.act.type
row['status_receiver_created'] = self.act.created trade['status_receiver_created'] = self.act.created
self.rows.append(row) return
# import pdb; pdb.set_trace()
# necesitamos poder poner un cambio de estado de un trade mas antiguo que last_trade
# lo mismo con confirm
def get_snapshot(self): def get_snapshot(self):
""" """
@ -97,6 +105,7 @@ class Metrics(MetricsMix):
""" """
If the action is one Allocate, need modify the row base. If the action is one Allocate, need modify the row base.
""" """
self.action_create_by = 'Receiver'
self.end_users = self.act.end_users self.end_users = self.act.end_users
self.final_user_code = self.act.final_user_code self.final_user_code = self.act.final_user_code
row = self.get_template_row() row = self.get_template_row()
@ -112,6 +121,7 @@ class Metrics(MetricsMix):
""" """
If the action is one Live, need modify the row base. If the action is one Live, need modify the row base.
""" """
self.action_create_by = 'Receiver'
row = self.get_template_row() row = self.get_template_row()
row['type'] = 'Live' row['type'] = 'Live'
row['finalUserCode'] = self.final_user_code row['finalUserCode'] = self.final_user_code
@ -127,6 +137,7 @@ class Metrics(MetricsMix):
""" """
If the action is one Dellocate, need modify the row base. If the action is one Dellocate, need modify the row base.
""" """
self.action_create_by = 'Receiver'
row = self.get_template_row() row = self.get_template_row()
row['type'] = 'Deallocate' row['type'] = 'Deallocate'
row['start'] = self.act.start_time row['start'] = self.act.start_time
@ -147,15 +158,18 @@ class Metrics(MetricsMix):
""" """
if self.act.author == self.act.user_from: if self.act.author == self.act.user_from:
self.action_create_by = 'Supplier' self.action_create_by = 'Supplier'
self.status_receiver = ''
row = self.get_template_row() row = self.get_template_row()
self.last_trade = row self.last_trade = row
row['type'] = 'Trade' row['type'] = 'Trade'
row['action_type'] = 'Trade' row['action_type'] = 'Trade'
row['trade_supplier'] = self.act.user_from.email row['trade_supplier'] = self.act.user_from.email
row['trade_receiver'] = self.act.user_to.email row['trade_receiver'] = self.act.user_to.email
row['self.status_receiver'] = self.status_receiver row['status_receiver'] = self.status_receiver
row['self.status_supplier'] = self.status_supplier row['status_supplier'] = ''
row['trade_confirmed'] = self.get_confirms() row['trade_confirmed'] = self.get_confirms()
self.trades[self.act] = row
self.rows.append(row) self.rows.append(row)
def get_metrics(self): def get_metrics(self):
@ -219,9 +233,9 @@ class TradeMetrics(MetricsMix):
row['status_receiver'] = '' row['status_receiver'] = ''
row['status_supplier'] = '' row['status_supplier'] = ''
row['trade_weight'] = self.document.weight row['trade_weight'] = self.document.weight
if self.last_trade.author == self.last_trade.user_from: if self.document.owner == self.last_trade.user_from:
row['action_create_by'] = 'Supplier' row['action_create_by'] = 'Supplier'
elif self.last_trade.author == self.last_trade.user_to: elif self.document.owner == self.last_trade.user_to:
row['action_create_by'] = 'Receiver' row['action_create_by'] = 'Receiver'
self.rows.append(row) self.rows.append(row)
@ -233,8 +247,20 @@ class TradeMetrics(MetricsMix):
if the action is one trade action, is possible than have a list of confirmations. if the action is one trade action, is possible than have a list of confirmations.
Get the doble confirm for to know if this trade is confirmed or not. Get the doble confirm for to know if this trade is confirmed or not.
""" """
if hasattr(self.last_trade, 'acceptances_document'): trade = None
accept = self.last_trade.acceptances_document[-1] confirmations = []
if accept.t == 'Confirm' and accept.user == self.last_trade.user_to: confirms = []
for ac in self.document.actions:
if ac.t == 'Trade':
trade = ac
elif ac.t == 'ConfirmDocument':
confirms.append(ac.author)
confirmations.append(ac)
elif ac.t in ['RevokeDocument', 'ConfirmDocumentRevoke']:
confirmations.append(ac)
if confirmations and confirmations[-1].t == 'ConfirmDocument':
if trade.user_from in confirms and trade.user_to in confirms:
return True return True
return False return False

View File

@ -171,7 +171,16 @@ class Device(Thing):
Actions are returned by descending ``created`` time. Actions are returned by descending ``created`` time.
""" """
return sorted(chain(self.actions_multiple, self.actions_one), key=lambda x: x.created) actions_multiple = copy.copy(self.actions_multiple)
actions_one = copy.copy(self.actions_one)
for ac in actions_multiple:
ac.real_created = ac.actions_device[0].created
for ac in actions_one:
ac.real_created = ac.created
return sorted(chain(actions_multiple, actions_one), key=lambda x: x.real_created)
@property @property
def problems(self): def problems(self):
@ -635,7 +644,13 @@ class Computer(Device):
@property @property
def actions(self) -> list: def actions(self) -> list:
return sorted(chain(super().actions, self.actions_parent)) actions = copy.copy(super().actions)
actions_parent = copy.copy(self.actions_parent)
for ac in actions_parent:
ac.real_created = ac.created
return sorted(chain(actions, actions_parent), key=lambda x: x.real_created)
# return sorted(chain(super().actions, self.actions_parent))
@property @property
def ram_size(self) -> int: def ram_size(self) -> int:

View File

@ -435,7 +435,7 @@ class ActionRow(OrderedDict):
self['Action-User-LastOwner-Receiver'] = allocate['trade_receiver'] self['Action-User-LastOwner-Receiver'] = allocate['trade_receiver']
self['Action-Create-By'] = allocate['action_create_by'] self['Action-Create-By'] = allocate['action_create_by']
self['Trade-Confirmed'] = allocate['trade_confirmed'] self['Trade-Confirmed'] = allocate['trade_confirmed']
self['Status-Supplier'] = allocate['status_supplier'] self['Status-Created-By-Supplier-About-Reciber'] = allocate['status_supplier']
self['Status-Receiver'] = allocate['status_receiver'] self['Status-Receiver'] = allocate['status_receiver']
self['Status Supplier Created Date'] = allocate['status_supplier_created'] self['Status Supplier Created Date'] = allocate['status_supplier_created']
self['Status Receiver Created Date'] = allocate['status_receiver_created'] self['Status Receiver Created Date'] = allocate['status_receiver_created']

View File

@ -134,7 +134,7 @@ def test_metrics_action_status(user: UserClient, user2: UserClient):
item='actions/', item='actions/',
accept='text/csv', accept='text/csv',
query=[('filter', {'type': ['Computer']})]) query=[('filter', {'type': ['Computer']})])
head = 'DHID;Hid;Document-Name;Action-Type;Action-User-LastOwner-Supplier;Action-User-LastOwner-Receiver;Action-Create-By;Trade-Confirmed;Status-Supplier;Status-Receiver;Status Supplier Created Date;Status Receiver Created Date;Trade-Weight;Action-Create;Allocate-Start;Allocate-User-Code;Allocate-NumUsers;UsageTimeAllocate;Type;LiveCreate;UsageTimeHdd\n' head = 'DHID;Hid;Document-Name;Action-Type;Action-User-LastOwner-Supplier;Action-User-LastOwner-Receiver;Action-Create-By;Trade-Confirmed;Status-Created-By-Supplier-About-Reciber;Status-Receiver;Status Supplier Created Date;Status Receiver Created Date;Trade-Weight;Action-Create;Allocate-Start;Allocate-User-Code;Allocate-NumUsers;UsageTimeAllocate;Type;LiveCreate;UsageTimeHdd\n'
body = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Status;;foo@foo.com;Receiver;;;Use;;' body = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Status;;foo@foo.com;Receiver;;;Use;;'
assert head in csv_str assert head in csv_str
assert body in csv_str assert body in csv_str
@ -156,6 +156,10 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient):
res=Lot, res=Lot,
item='{}/devices'.format(lot['id']), item='{}/devices'.format(lot['id']),
query=devices) query=devices)
action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]}
user.post(action, res=ma.Action)
request_post = { request_post = {
'type': 'Trade', 'type': 'Trade',
'devices': [snap1['device']['id'], snap2['device']['id']], 'devices': [snap1['device']['id'], snap2['device']['id']],
@ -169,34 +173,44 @@ def test_complet_metrics_with_trade(user: UserClient, user2: UserClient):
user.post(res=ma.Action, data=request_post) user.post(res=ma.Action, data=request_post)
action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]} action = {'type': ma.Use.t, 'devices': [snap1['device']['id']]}
action_use, _ = user.post(action, res=ma.Action) action_use, _ = user.post(action, res=ma.Action)
csv_str, _ = user.get(res=documents.DocumentDef.t, csv_str, _ = user.get(res=documents.DocumentDef.t,
item='actions/', item='actions/',
accept='text/csv', accept='text/csv',
query=[('filter', {'type': ['Computer']})]) query=[('filter', {'type': ['Computer']})])
body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;foo2@foo.com;Supplier;False;Refurbish;Use;' body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;'
body1_lenovo += 'foo2@foo.com;Supplier;False;Use;;'
body2_lenovo = ';;0;0;Trade;0;0\n' body2_lenovo = ';;0;0;Trade;0;0\n'
body1_acer = 'J2MA2;laptop-acer-aohappy-lusea0d010038879a01601-00:26:c7:8e:cb:8c;;Trade;foo@foo.com;foo2@foo.com;Supplier;False;;Use;;;0;' body1_acer = 'J2MA2;laptop-acer-aohappy-lusea0d010038879a01601-00:26:c7:8e:cb:8c;;Trade;'
body1_acer += 'foo@foo.com;foo2@foo.com;Supplier;False;;;;;0;'
body2_acer = ';;0;0;Trade;0;4692.0\n' body2_acer = ';;0;0;Trade;0;4692.0\n'
# import pdb; pdb.set_trace()
assert body1_lenovo in csv_str assert body1_lenovo in csv_str
assert body2_lenovo in csv_str assert body2_lenovo in csv_str
assert body1_acer in csv_str assert body1_acer in csv_str
assert body2_acer in csv_str assert body2_acer in csv_str
# User2 mark this device as Refurbish # User2 mark this device as Refurbish
action = {'type': ma.Refurbish.t, 'devices': [snap1['device']['id']]} action = {'type': ma.Use.t, 'devices': [snap1['device']['id']]}
action_use2, _ = user2.post(action, res=ma.Action) action_use2, _ = user2.post(action, res=ma.Action)
csv_str, _ = user.get(res=documents.DocumentDef.t, csv_str, _ = user.get(res=documents.DocumentDef.t,
item='actions/', item='actions/',
accept='text/csv', accept='text/csv',
query=[('filter', {'type': ['Computer']})]) query=[('filter', {'type': ['Computer']})])
body2_lenovo = ';Refurbish;0;0;Trade;0;0\n' body1_lenovo = 'O48N2;desktop-lenovo-9644w8n-0169622-00:1a:6b:5e:7f:10;;Trade;foo@foo.com;'
body2_acer = ';Refurbish;0;0;Trade;0;4692.0\n' body1_lenovo += 'foo2@foo.com;Supplier;False;Use;Use;'
body2_lenovo = ';;0;0;Trade;0;0\n'
body2_acer = ';;0;0;Trade;0;4692.0\n'
assert body1_lenovo in csv_str
assert body2_lenovo in csv_str
assert body2_acer in csv_str
@pytest.mark.mvp @pytest.mark.mvp