Skip to content

Commit 642b287

Browse files
authored
Merge pull request #24 from CampusPulse/manual-status
Allow admins to manually update status
2 parents 7e83261 + d712b8d commit 642b287

File tree

2 files changed

+143
-18
lines changed

2 files changed

+143
-18
lines changed

app.py

Lines changed: 109 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@
4545
from flask_migrate import Migrate, stamp, upgrade
4646
from flask_cors import CORS, cross_origin
4747
from s3 import S3Bucket
48-
from typing import Optional
48+
from typing import Optional, Union
4949
import shutil
5050
import pandas as pd
5151
import json_log_formatter
5252
from pathlib import Path
5353
from dotenv import load_dotenv
54-
from helpers import floor_to_integer, RoomNumber, integer_to_floor, MapLocation, ServiceNowStatus, ServiceNowUpdateType, save_user_details, check_for_admin_role, get_logged_in_user_id, get_logged_in_user
54+
from helpers import floor_to_integer, RoomNumber, integer_to_floor, MapLocation, ServiceNowStatus, ServiceNowUpdateType, save_user_details, check_for_admin_role, get_logged_in_user_id, get_logged_in_user, get_logged_in_user_info
5555
from urllib.parse import quote_plus, urlencode
5656
from authlib.integrations.flask_client import OAuth
5757

@@ -249,6 +249,24 @@ def access_point_json(access_point: AccessPoint):
249249
})
250250
return base_data
251251

252+
def access_point_admin_json(access_point: AccessPoint):
253+
254+
status = get_item_status(access_point)
255+
report = get_item_report(access_point)
256+
257+
status_categories = {i.name: i.value for i in StatusType}
258+
259+
260+
# TODO: use marshmallow to serialize
261+
admin_data = {
262+
"status_ticket_number": (status.report.ref or "No Ticket") if status else "No Status",
263+
"status_report_id": status.report.id,
264+
"report_id": report.id,
265+
"status_categories": status_categories
266+
}
267+
268+
return admin_data
269+
252270

253271
"""
254272
Creates a geojson for map feature
@@ -597,7 +615,7 @@ def import_data(file):
597615
"""
598616

599617

600-
def getAccessPoint(id):
618+
def getAccessPoint(id, is_admin=False):
601619
access_point = db.session.execute(
602620
db.select(AccessPoint).where(AccessPoint.id == id)
603621
).scalar()
@@ -608,6 +626,8 @@ def getAccessPoint(id):
608626
return None
609627

610628
accessPointInfo = access_point_json(access_point)
629+
if is_admin:
630+
accessPointInfo.update(access_point_admin_json(access_point))
611631
logging.debug(accessPointInfo)
612632
return accessPointInfo
613633

@@ -910,15 +930,18 @@ def tags():
910930

911931
@app.route("/access_points/<id>")
912932
def access_point(id):
913-
if checkAccessPointExists(id):
914-
return render_template(
915-
"access_point.html",
916-
authsession=get_logged_in_user(),
917-
is_admin=check_for_admin_role(get_logged_in_user_id()),
918-
accessPointDetails=getAccessPoint(id)
919-
)
920-
else:
933+
if not checkAccessPointExists(id):
921934
return render_template("404.html"), 404
935+
936+
is_admin = check_for_admin_role(get_logged_in_user_id())
937+
return render_template(
938+
"access_point.html",
939+
authsession=get_logged_in_user(),
940+
is_admin=is_admin,
941+
accessPointDetails=getAccessPoint(id, is_admin=is_admin)
942+
943+
)
944+
922945

923946

924947
########################
@@ -1138,6 +1161,53 @@ def add_ticket(item_id):
11381161

11391162
return ("", 200)
11401163

1164+
1165+
@app.route("/add_status/<item_id>", methods=["POST"])
1166+
@requires_admin
1167+
def add_status(item_id):
1168+
if not checkAccessPointExists(item_id):
1169+
return "Not found", 404
1170+
1171+
status_text = request.form.get("status")
1172+
note_text = request.form.get("note")
1173+
category = StatusType(int(request.form.get("category")))
1174+
author = get_logged_in_user_info()
1175+
author_identifier = f" - manually added by {author['name']} ({author['sub']}) via web UI"
1176+
1177+
note_text += author_identifier
1178+
1179+
prior_report = get_item_report(item_id)
1180+
current_report = None
1181+
1182+
previous_report_has_ticket = prior_report.ref is not None
1183+
1184+
if previous_report_has_ticket or category == StatusType.BROKEN:
1185+
current_report = Report()
1186+
db.session.add(current_report)
1187+
db.session.flush()
1188+
1189+
# associate new report with this access point
1190+
association = AccessPointReports(
1191+
report_id=current_report.id,
1192+
access_point_id=item_id
1193+
)
1194+
db.session.add(association)
1195+
1196+
else:
1197+
current_report = prior_report
1198+
1199+
status_update = Status(
1200+
report = current_report,
1201+
status = status_text,
1202+
status_type = category,
1203+
timestamp = datetime.utcnow(),
1204+
notes = note_text
1205+
)
1206+
db.session.add(status_update)
1207+
db.session.commit()
1208+
1209+
return redirect(f"/access_points/{item_id}")
1210+
11411211
########################
11421212
#
11431213
# region Management Helpers
@@ -1225,25 +1295,48 @@ def associate_thumbnail(file_hash, thumbnail_file, item_identifier):
12251295
db.session.commit()
12261296

12271297

1228-
def get_item_status(item):
1229-
"""Fetch the status for the provided item.
1298+
def get_item_status(item: Union[AccessPoint, int]):
1299+
"""Fetch the most recent status for the provided item.
12301300
12311301
Args:
1232-
item (AccessPoint): The item (in this case AccessPoint) to fetch status for
1302+
item (Union[AccessPoint, int]): The item (in this case AccessPoint) to fetch status for (or its integer ID)
12331303
12341304
Returns:
12351305
Status: the status of the access point, or None if none were found
12361306
"""
1237-
1307+
item_id = item.id if isinstance(item, AccessPoint) else item
12381308
status = db.session.execute(
12391309
db.select(Status)
12401310
.join(AccessPointReports, AccessPointReports.report_id == Status.report_id)
1241-
.where(AccessPointReports.access_point_id == item.id)
1311+
.where(AccessPointReports.access_point_id == item_id)
12421312
.order_by(Status.timestamp.desc())
12431313
).scalars().first()
12441314

12451315
return status
12461316

1317+
def get_item_report(item:Union[AccessPoint, int]):
1318+
"""Fetch the latest report for the provided item.
1319+
1320+
While you can get this using get_item_status and accessing it through the associated report,
1321+
that method can miss scenarios where a report has been created but there is no status yet
1322+
This can happen when associating a ticket before any email has come in yet.
1323+
1324+
Args:
1325+
item (Union[AccessPoint, int]): The item (in this case AccessPoint) to fetch the report for (or its integer ID)
1326+
1327+
Returns:
1328+
Report: the report of the access point, or None if none were found
1329+
"""
1330+
item_id = item.id if isinstance(item, AccessPoint) else item
1331+
report = db.session.execute(
1332+
db.select(Report)
1333+
.join(AccessPointReports, AccessPointReports.report_id == Report.id)
1334+
.where(AccessPointReports.access_point_id == item_id)
1335+
.order_by(Report.id.desc())
1336+
).scalars().first()
1337+
1338+
return report
1339+
12471340

12481341
"""
12491342
Delete tag and all relations from DB

templates/access_point.html

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ <h3>Location:</h3>
117117
<br>{% endif %}
118118
<strong>Room</strong>: {{ accessPointDetails["room"] }}<br>
119119
<strong>Status Last Updated</strong>: {{ accessPointDetails["status_updated"] }}<br>
120+
{% if authsession and is_admin %}
121+
<strong>Last Report Ticket #</strong>: {{ accessPointDetails["status_ticket_number"] }}<br>
122+
<strong>Last Report ID #</strong>: {{ accessPointDetails["report_id"] }}<br>
123+
<strong>Last Report ID #</strong>: {{ accessPointDetails["status_report_id"] }} (By Status)<br>
124+
{% endif %}
120125
</p>
121126

122127

@@ -188,11 +193,38 @@ <h2 class="accordion-header" id="headingOne">
188193
{% if authsession and is_admin %}
189194

190195
<form action="/add_ticket/{{accessPointDetails['id']}}" method="post">
191-
<label for="ticket_ref" class="form-label"><b>Ticket Number</b></label>
196+
<details>
197+
<summary><label for="ticket_ref" class="form-label"><b>Assign Ticket Number</b></label></summary>
192198
<p class="thin">Filed a ticket for this item? Ensure the system email is on the watch list and add the ticket number here!</p>
193-
<input type="text" class="form-control" id="ticket_ref" name="ticket_ref" placeholder="WOT1234567" required >
199+
<input type="text" class="form-control" id="ticket_ref" name="ticket_ref" placeholder="WOT1234567" required pattern="WOT[0-9]+" title="Work order ticket number like WOT1234567">
194200
<button type="submit" class="btn btn-primary">Submit</button>
201+
</details>
202+
</form>
203+
204+
<form action="/add_status/{{accessPointDetails['id']}}" method="post">
205+
<details>
206+
<summary><b>Assign Manual Status</b></summary>
207+
<p class="thin">Sign on the elevator? Heard from another channel that its broken? Update it here! Don't forget to come back and update it again if it is fixed!</p>
208+
<label>
209+
Status Bubble Text:
210+
<input type="text" class="form-control" id="status" name="status" title="The name for the status bubble (1-30 char)" required pattern=".{1,30}" placeholder="Reported">
211+
</label>
212+
<label>
213+
Note
214+
<input type="text" class="form-control" id="note" name="note" title="Brief note about why this status changed" required placeholder="Theres a sign on the door">
215+
</label>
216+
<label>
217+
Category
218+
<select name="category">
219+
{% for name, val in accessPointDetails['status_categories'].items() %}
220+
<option value="{{val}}">{{name}}</option>
221+
{% endfor %}
222+
</select>
223+
</label><br>
224+
<button type="submit" class="btn btn-primary">Submit</button>
225+
</details>
195226
</form>
227+
196228
{% endif %}
197229
</div>
198230
</div>

0 commit comments

Comments
 (0)