Skip to content
This repository has been archived by the owner on Dec 19, 2021. It is now read-only.

Initial refactoring of the dashboard logic #393

Merged
merged 12 commits into from
Oct 25, 2021
2 changes: 2 additions & 0 deletions config/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from resources.notes import Notes, Note
from resources.lease import Lease, Leases
from resources.widgets import Widgets
from resources.dashboard_resource import DashboardResource


class Routes:
Expand All @@ -35,6 +36,7 @@ def routing(app):
api.add_resource(ArchiveUser, "user/archive/<int:user_id>")
api.add_resource(UserLogin, "login")
api.add_resource(Widgets, "widgets")
api.add_resource(DashboardResource, "dashboard")
api.add_resource(UserAccessRefresh, "refresh")
api.add_resource(StaffTenants, "staff-tenants")
api.add_resource(Tenants, "tenants")
Expand Down
27 changes: 14 additions & 13 deletions doc/using_the_api.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,31 +268,32 @@ All endpoints are prefixed with `/api/`

```javascript
{
'opentickets': {
"opentickets": {
"new": {
"allNew": {
"stat": TicketModel.find_count_by_status("New"),
"desc": "New",
"stat": 7,
},
"unseen24Hrs": {
"stat": TicketModel.find_count_by_update_status("New", 1440),
"desc": "Unseen for > 24 hours",
"stat": 2,
},
},
"inProgress": {
"allInProgress": {
"stat": TicketModel.find_count_by_status("In Progress"),
"desc": "In Progress",
"stat": 10,
},
"inProgress1Week": {
"stat": TicketModel.find_count_by_update_status(
"In Progress", 10080
),
"desc": "In progress for > 1 week",
"stat": 3,
},
},
},
'managers': projectManagers
"managers": [
{
"date": "Today",
"firstName": "",
"id": 1,
"lastName": "Smith",
"propertyName": ""
},
],
}

```
67 changes: 67 additions & 0 deletions models/dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from models.tickets import TicketModel
from models.users.property_manager import PropertyManager
from utils.time import Date


class Dashboard:
@staticmethod
def json():
return {
"opentickets": {
"new": {
"allNew": {
"stat": TicketModel.new().count(),
},
"unseen24Hrs": {
"stat": TicketModel.new().updated_within(days=1).count(),
},
},
"inProgress": {
"allInProgress": {
"stat": TicketModel.in_progress().count(),
},
"inProgress1Week": {
"stat": TicketModel.in_progress()
.updated_within(days=7)
.count(),
},
},
},
"managers": [Dashboard._manager_json(m) for m in PropertyManager.take(3)],
}

# Below is what I would like to use instead of the above json^^^
@staticmethod
def proposed_json():
return {
"new_count": TicketModel.new().count(),
"recent_new_count": TicketModel.new().updated_within(days=1).count(),
"in_progress_count": TicketModel.in_progress().count(),
"recent_in_progress_count": TicketModel.in_progress()
.updated_within(days=7)
.count(),
"managers": [Dashboard._manager_json(m) for m in PropertyManager.take(3)],
}

@staticmethod
def _manager_json(manager):
return {
"id": manager.id,
"firstName": manager.firstName,
"lastName": manager.lastName,
"date": Dashboard._humanize_date(manager.created_at.date()),
"propertyName": manager.properties[0].name
if len(manager.properties) > 0
else "Not Assigned",
}

@staticmethod
def _humanize_date(date):
if date == Date.today():
return "Today"
elif date == Date.days_ago(1):
return "Yesterday"
elif date >= Date.weeks_ago(1):
return "This Week"

return date.strftime("%m/%d")
37 changes: 18 additions & 19 deletions models/tickets.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import re

from db import db
from nobiru.nobiru_list import NobiruList
from datetime import datetime, timedelta
from datetime import datetime
from models.base_model import BaseModel
from utils.time import Time


class TicketModel(BaseModel):
STATUSES = ("New", "In Progress", "Closed")

NEW = STATUSES[0]
IN_PROGRESS = STATUSES[1]
CLOSED = STATUSES[2]

for status in STATUSES:
name = re.sub(r"\s", "_", status.lower())
exec(
f"""
@classmethod
def {name}(cls):
return cls.query.filter_by(status='{status}')
"""
)

__tablename__ = "tickets"

id = db.Column(db.Integer, primary_key=True)
issue = db.Column(db.String(144))
tenant_id = db.Column(db.Integer, db.ForeignKey("tenants.id"), nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
status = db.Column(db.String, default=STATUSES[0], nullable=False)
status = db.Column(db.String, default=NEW, nullable=False)
urgency = db.Column(db.String(12))

notes = db.relationship(
Expand Down Expand Up @@ -43,20 +59,3 @@ def json(self):
"created_at": Time.format_date(self.created_at),
"updated_at": Time.format_date(self.updated_at),
}

# Get tickets with the given status
@classmethod
def find_count_by_status(cls, status):
return cls.query.filter_by(status=status).count()

# Get tickets updated with the given time and with the given status
@classmethod
def find_count_by_update_status(cls, status, minutes):
# calculated in minutes: 1 day = 1440, 1 week = 10080
dateTime = datetime.utcnow() - timedelta(minutes=minutes)
return (
db.session.query(TicketModel)
.filter(TicketModel.updated_at >= dateTime)
.filter(TicketModel.status == status)
.count()
)
10 changes: 2 additions & 8 deletions models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,8 @@ def find_by_email(cls, email):
return cls.query.filter_by(email=email).first()

@classmethod
def find_recent_role(cls, role, days):
return (
db.session.query(UserModel)
.filter(UserModel.role == role)
.order_by(UserModel.created_at.desc())
.limit(3)
.all()
)
def take(cls, num):
return cls.query.order_by(cls.created_at.desc()).limit(num).all()

@classmethod
def find_by_role_and_name(cls, role, name):
Expand Down
6 changes: 6 additions & 0 deletions nobiru/nobiru_query.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
from nobiru.nobiru import Nobiru
from flask_sqlalchemy import BaseQuery
from sqlalchemy import column

from utils.time import TimeStamp


class NobiruQuery(BaseQuery):
def json(self):
return Nobiru.json(self)

def updated_within(self, days=None):
return self.filter(column("updated_at") >= TimeStamp.days_ago(days))
10 changes: 10 additions & 0 deletions resources/dashboard_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from flask_restful import Resource
from utils.authorizations import pm_level_required

from models.dashboard import Dashboard


class DashboardResource(Resource):
@pm_level_required
def get(self):
return Dashboard.proposed_json()
75 changes: 3 additions & 72 deletions resources/widgets.py
Original file line number Diff line number Diff line change
@@ -1,79 +1,10 @@
from flask_restful import Resource
from models.tickets import TicketModel
from models.user import UserModel
from models.property import PropertyModel
from utils.authorizations import pm_level_required
from datetime import datetime, timedelta

from models.dashboard import Dashboard

class Widgets(Resource):
def dateStringConversion(self, date):
stat = date.strftime("%m/%d")
today = datetime.utcnow()
yesterday = today - timedelta(days=1)
week = today - timedelta(days=1)

if date.date() == today.date():
stat = "Today"
elif date.date() == yesterday.date():
stat = "Yesterday"
elif date.date() >= week.date() and date.date() < yesterday.date():
stat = "This Week"

return stat

def returnPropertyName(self, userID):
# returns the first property to keep things tidy, could add feature later
property = PropertyModel.find_by_manager(userID)
propertyName = "Not Assigned"

if len(property):
propertyName = property[0].name

return propertyName

class Widgets(Resource):
@pm_level_required
def get(self):
users = UserModel.find_recent_role("PROPERTY_MANAGER", 5)
projectManagers = []

for user in users:
date = self.dateStringConversion(user.created_at)
propertyName = self.returnPropertyName(user.id)
projectManagers.append(
{
"id": user.id,
"firstName": user.firstName,
"lastName": user.lastName,
"date": date,
"propertyName": propertyName,
}
)

return {
"opentickets": {
"new": {
"allNew": {
"stat": TicketModel.find_count_by_status("New"),
"desc": "New",
},
"unseen24Hrs": {
"stat": TicketModel.find_count_by_update_status("New", 1440),
"desc": "Unseen for > 24 hours",
},
},
"inProgress": {
"allInProgress": {
"stat": TicketModel.find_count_by_status("In Progress"),
"desc": "In Progress",
},
"inProgress1Week": {
"stat": TicketModel.find_count_by_update_status(
"In Progress", 10080
),
"desc": "In progress for > 1 week",
},
},
},
"managers": projectManagers,
}, 200
return Dashboard.json()
4 changes: 2 additions & 2 deletions tests/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ def lease_attrs(faker, unitNum=None, dateTimeStart=None, dateTimeEnd=None):
}


def ticket_attrs(faker, issue=None):
def ticket_attrs(faker, issue=None, status=None):
return {
"issue": issue or faker.sentence(),
"urgency": faker.random_element(("Low", "Medium", "High")),
"status": faker.random_element(TicketModel.STATUSES),
"status": status or faker.random_element(TicketModel.STATUSES),
}


Expand Down
28 changes: 19 additions & 9 deletions tests/factory_fixtures/ticket.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,35 @@

@pytest.fixture
def ticket_attributes(faker, create_tenant, create_join_staff):
def _ticket_attributes(issue=None, tenant=None, author=None):
return {
**ticket_attrs(faker),
def _ticket_attributes(
issue=None,
status=None,
tenant=None,
author=None,
created_at=None,
updated_at=None,
):
attrs = {
**ticket_attrs(faker, issue, status),
"tenant_id": tenant.id if tenant else create_tenant().id,
"author_id": author.id if author else create_join_staff().id,
}
if created_at:
attrs["created_at"] = created_at
if updated_at:
attrs["updated_at"] = updated_at

return attrs

yield _ticket_attributes


@pytest.fixture
def create_ticket(faker, ticket_attributes, create_tenant, create_join_staff):
def _create_ticket(issue=None):
if not issue:
issue = faker.sentence()
tenant = create_tenant()
def create_ticket(faker, ticket_attributes):
def _create_ticket(**kwargs):
ticket = TicketModel.create(
schema=TicketSchema,
payload=ticket_attributes(issue, tenant, create_join_staff()),
payload=ticket_attributes(**kwargs),
)
ticket.save_to_db()
return ticket
Expand Down
Loading