Player Ticket History - A Synopsis At A Glance (#75830)

## About The Pull Request

Adds a new front-end for viewing player ticket history stored in the
connected database.
Also adds a button to directly query for previous tickets from the
ticket panel.

## Pictures


![image](https://github.com/tgstation/tgstation/assets/12817816/4c01fb8b-3cf0-4d6c-a281-8eef2ff8f37c)

![image](https://github.com/tgstation/tgstation/assets/12817816/739f8b11-c064-4e90-bdae-22a4cc50c63d)

![image](https://github.com/tgstation/tgstation/assets/12817816/e4665d98-7bf9-4806-9d78-c8133682f7ae)

![image](https://github.com/tgstation/tgstation/assets/12817816/450edafc-b3b1-4013-8511-8328d944f440)
## Why It's Good For The Game

Gives admins the ability to look at a players behaviour in-game without
the need for external tooling, (statbus)
## Changelog

🆑
admin: Player Ticket History - view a player's ticket history without
the need to use external tooling.
/🆑
This commit is contained in:
Zephyr
2023-06-07 10:51:26 +12:00
committed by GitHub
parent 86ba209d5b
commit 89f5992a4c
6 changed files with 450 additions and 0 deletions
+1
View File
@@ -92,6 +92,7 @@ GLOBAL_PROTECT(admin_verbs_admin)
/client/proc/toggle_view_range, /*changes how far we can see*/
/client/proc/cmd_admin_law_panel,
/client/proc/log_viewer_new,
/client/proc/player_ticket_history,
)
GLOBAL_LIST_INIT(admin_verbs_ban, list(/client/proc/unban_panel, /client/proc/ban_panel, /client/proc/stickybanpanel, /client/proc/library_control))
GLOBAL_PROTECT(admin_verbs_ban)
+9
View File
@@ -1122,6 +1122,15 @@
return
M.mind_initialize()
else if(href_list["player_ticket_history"])
if(!check_rights(R_ADMIN))
return
var/target_ckey = href_list["player_ticket_history"]
GLOB.player_ticket_history.cache_history_for_ckey(target_ckey)
GLOB.player_ticket_history.user_selections[usr.ckey] = target_ckey
GLOB.player_ticket_history.ui_interact(usr)
return
else if(href_list["create_object"])
if(!check_rights(R_SPAWN))
return
+4
View File
@@ -573,6 +573,10 @@ GLOBAL_DATUM_INIT(ahelp_tickets, /datum/admin_help_tickets, new)
for(var/I in ticket_interactions)
dat += "[I]<br>"
// Helper for opening directly to player ticket history
dat += "<br><br><b>Player Ticket History:</b>"
dat += "[FOURSPACES]<A href='?_src_=holder;[HrefToken()];player_ticket_history=[initiator_ckey]'>Open</A>"
// Append any tickets also opened by this user if relevant
var/list/related_tickets = GLOB.ahelp_tickets.TicketsByCKey(initiator_ckey)
if (related_tickets.len > 1)
@@ -0,0 +1,198 @@
/// Holds information about a ticket
/datum/ticket_history
var/player_ckey
/// Number of the ticket *for that round*
var/ticket_number
var/round_id
var/list/ticket_log
/datum/ticket_log_entry
var/timestamp
var/origin_ckey
var/target_ckey
var/action
var/message
GENERAL_PROTECT_DATUM(/datum/ticket_history)
GENERAL_PROTECT_DATUM(/datum/ticket_log_entry)
GLOBAL_DATUM_INIT(player_ticket_history, /datum/ticket_history_holder, new)
GLOBAL_PROTECT(player_ticket_history)
/client/proc/player_ticket_history()
set name = "Player Ticket History"
set desc = "Allows you to view the ticket history of a player."
set category = "Admin"
if(!check_rights(R_ADMIN))
return
GLOB.player_ticket_history.ui_interact(mob)
/datum/ticket_history_holder
/// Assosciative list of ticket histories. ckey -> list/datum/ticket_history
var/list/ticket_histories = list()
/// Assosciative list of user_ckey -> target_ckey
var/list/user_selections = list()
/datum/ticket_history_holder/proc/cache_history_for_ckey(ckey, entries = 5)
ckey = lowertext(ckey)
if(!isnum(entries) || entries <= 0)
return
var/list/datum/ticket_history/history_cache = list()
ticket_histories[ckey] = history_cache
var/datum/db_query/ticket_lookup = SSdbcore.NewQuery("\
WITH DISTINCT_TICKETS AS ( \
SELECT id, round_id, ticket, sender, recipient FROM [format_table_name("ticket")] \
WHERE id IN ( \
SELECT MAX(id) FROM ticket GROUP BY round_id, ticket \
) \
AND round_id != :current_round \
) \
SELECT round_id, ticket FROM DISTINCT_TICKETS \
WHERE sender = :ckey OR recipient = :ckey \
ORDER BY id DESC \
LIMIT :max_entries",
list(
"ckey" = ckey,
"current_round" = GLOB.round_id,
"max_entries" = entries,
)
)
if(!ticket_lookup.Execute())
qdel(ticket_lookup)
to_chat(usr, "Failed to query ticket history for [ckey]!")
return
var/list/lookup_targets = list()
// round-ticket
while(ticket_lookup.NextRow())
lookup_targets += "[ticket_lookup.item[1]]-[ticket_lookup.item[2]]"
qdel(ticket_lookup)
for(var/lookup_string in lookup_targets)
ASYNC
var/datum/ticket_history/ticket_history = new
history_cache += ticket_history
var/round = splittext(lookup_string, "-")[1]
var/ticket = splittext(lookup_string, "-")[2]
ticket_history.round_id = text2num(round)
ticket_history.ticket_number = text2num(ticket)
var/datum/db_query/ticket_lookup_instance = SSdbcore.NewQuery("\
SELECT action, message, timestamp, recipient, sender \
FROM [format_table_name("ticket")] \
WHERE round_id = :round AND ticket = :ticket \
ORDER BY id DESC \
", list(
"round" = round,
"ticket" = ticket
))
if(!ticket_lookup_instance.warn_execute())
qdel(ticket_lookup_instance)
lookup_targets -= lookup_string
continue
var/list/ticket_log = list()
while(ticket_lookup_instance.NextRow())
var/datum/ticket_log_entry/log_entry = new
ticket_log += log_entry
log_entry.action = ticket_lookup_instance.item[1]
log_entry.message = ticket_lookup_instance.item[2]
log_entry.timestamp = ticket_lookup_instance.item[3]
log_entry.target_ckey = ticket_lookup_instance.item[4]
log_entry.origin_ckey = ticket_lookup_instance.item[5]
qdel(ticket_lookup_instance)
ticket_history.ticket_log = ticket_log
lookup_targets -= lookup_string
// wait for all the queries to finish
UNTIL(lookup_targets.len == 0)
if(!length(history_cache))
to_chat(usr, span_adminnotice("No ticket history found for [ckey]!"))
ticket_histories -= ckey
return
to_chat(usr, span_adminnotice("Finished caching ticket history for [ckey]!"))
/datum/ticket_history_holder/ui_state(mob/user)
return GLOB.admin_state
/datum/ticket_history_holder/ui_static_data(mob/user)
if(!check_rights_for(CLIENT_FROM_VAR(user), R_ADMIN))
return list()
if(!SSdbcore.IsConnected())
return list(
"db_connected" = 0,
)
var/list/data = list(
"db_connected" = TRUE,
)
var/list/cached_ckeys = list()
for(var/ckey in ticket_histories)
cached_ckeys += ckey
data["cached_ckeys"] = cached_ckeys
if(user.ckey in user_selections)
var/list/ticket_cache = list()
for(var/datum/ticket_history/ticket_history as anything in ticket_histories[user_selections[user.ckey]])
var/list/ticket_data = list(
"ticket_number" = ticket_history.ticket_number,
"round_id" = ticket_history.round_id,
)
var/list/ticket_log = list()
for(var/datum/ticket_log_entry/entry as anything in ticket_history.ticket_log)
ticket_log += list(list(
"timestamp" = entry.timestamp,
"origin_ckey" = entry.origin_ckey,
"target_ckey" = entry.target_ckey,
"action" = entry.action,
"message" = entry.message,
))
ticket_data["ticket_log"] = ticket_log
ticket_cache += list(ticket_data)
data["ticket_cache"] = ticket_cache
data["target_ckey"] = user_selections[user.ckey]
return data
/datum/ticket_history_holder/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("select-user")
var/target = params["target"]
if(!(target in ticket_histories))
return TRUE
user_selections[ui.user.ckey] = target
SStgui.update_static_data(ui.user, ui)
return TRUE
if("cache-user")
var/target = params["target"]
var/amount = ("amount" in params) ? params["amount"] : 5
cache_history_for_ckey(target, amount)
SStgui.update_static_data(ui.user, ui)
return TRUE
else
stack_trace("[type]/ui_act: unknown action type [action]")
/datum/ticket_history_holder/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!isnull(ui))
ui.send_full_update()
return
ui = new(user, src, "PlayerTicketHistory")
ui.set_autoupdate(FALSE)
ui.open()