Records unhandled in-game bug reports in a DB table and loads the reports from it for the next round (#30710)

* initial table setup

* adds helpers to get full byond versions as numbers and adds those to the table as well

* reorder bug report new() proc and init bug_report_data as empty list instead of null

* more table changes. move adding the metadata to its own proc

* record unsent bug reports into the DB table

* refers to the correct index in the bug report data for the commit

* flip user and server byond versions

* jsonify bug report contents and metadata

* makes a bug report subsystem and moves recording to it

* Implements loading bug reports from the DB at shift start. Also removes handled bug reports from the DB directly

* Update SSbugreports.dm

* Update SSbugreports.dm

* scopes the bug report recording proc to the subsystem
This commit is contained in:
Migratingcocofruit
2025-11-01 16:06:34 +02:00
committed by GitHub
parent 32f4db8ab2
commit 1b2883e92e
8 changed files with 125 additions and 27 deletions
+1 -1
View File
@@ -440,7 +440,7 @@
#define INVESTIGATE_DEATHS "deaths"
// The SQL version required by this version of the code
#define SQL_VERSION 70
#define SQL_VERSION 71
// Vending machine stuff
#define CAT_NORMAL (1<<0)
@@ -0,0 +1,40 @@
SUBSYSTEM_DEF(bugreports)
name = "Bug Reports"
flags = SS_NO_FIRE
/datum/controller/subsystem/bugreports/Initialize()
// We just want to load all bug reports into the
var/datum/db_query/query_bug_reports = SSdbcore.NewQuery("SELECT * FROM bug_reports")
if(!query_bug_reports.warn_execute())
log_debug("Failed to load stored bug reports from DB")
qdel(query_bug_reports)
return
while(query_bug_reports.NextRow())
// Create a new bug report form an load the details from the current row into it
var/datum/tgui_bug_report_form/bug_report = new()
GLOB.bug_reports += bug_report
bug_report.db_uid = query_bug_reports.item[1]
bug_report.initial_key = query_bug_reports.item[2]
bug_report.bug_report_data = json_decode(query_bug_reports.item[5])
bug_report.awaiting_approval = TRUE
/datum/controller/subsystem/bugreports/Shutdown()
record_bug_reports()
/datum/controller/subsystem/bugreports/proc/record_bug_reports()
for(var/datum/tgui_bug_report_form/bug_report in GLOB.bug_reports)
var/datum/db_query/bug_query = SSdbcore.NewQuery({"
INSERT IGNORE INTO bug_reports (db_uid, author_ckey, title, round_id, contents_json) VALUES (:db_uid, :author_ckey, :title, :round_id, :contents_json)
"},
list(
"db_uid" = bug_report.db_uid,
"author_ckey" = bug_report.initial_key,
"title" = bug_report.bug_report_data["title"],
"round_id" = bug_report.bug_report_data["round_id"],
"contents_json" = json_encode(bug_report.bug_report_data),
)
)
bug_query.warn_execute()
qdel(bug_query)
GLOB.bug_reports -= bug_report
qdel(bug_report)
+56 -24
View File
@@ -6,11 +6,12 @@ GLOBAL_LIST_EMPTY(bug_report_time)
/datum/tgui_bug_report_form
/// contains all the body text for the bug report.
var/list/bug_report_data = null
var/list/bug_report_data = list()
/// client of the bug report author, needed to create the ticket
var/initial_user_uid = null
// ckey of the author
/// ckey of the author
var/initial_key = null // just incase they leave after creating the bug report
/// client of the admin/dev who is accessing the report, we don't want multiple people unknowingly making changes at the same time.
@@ -22,24 +23,27 @@ GLOBAL_LIST_EMPTY(bug_report_time)
/// for garbage collection purposes.
var/selected_confirm = FALSE
/// byond version of the user, so we still have the byond version if the user logs out
var/user_byond_version
/// Current Server commit
var/local_commit
/// Current test merges formatted for the bug report
var/test_merges
/// UID for DB stuff
var/db_uid
/datum/tgui_bug_report_form/New(mob/user)
local_commit = GLOB.revision_info.commit_hash
initial_user_uid = user.client.UID()
initial_key = user.client.key
user_byond_version = "[user.client.byond_version].[user.client.byond_build]"
if(length(GLOB.revision_info.origin_commit))
local_commit = GLOB.revision_info.origin_commit
if(user)
initial_user_uid = user.client.UID()
initial_key = user.client.key
/datum/tgui_bug_report_form/proc/add_metadata(mob/user)
bug_report_data["server_byond_version"] = "[world.byond_version].[world.byond_build]"
bug_report_data["user_byond_version"] = "[user.client.byond_version].[user.client.byond_build]"
bug_report_data["local_commit"] = "No Commit Data"
if(GLOB.revision_info.commit_hash)
bug_report_data["local_commit"] = GLOB.revision_info.commit_hash
if(GLOB.revision_info?.origin_commit && length(GLOB.revision_info.origin_commit))
bug_report_data["local_commit"] = GLOB.revision_info.origin_commit
for(var/datum/tgs_revision_information/test_merge/tm in GLOB.revision_info.testmerges)
test_merges += "#[tm.number] at [tm.head_commit]\n"
bug_report_data["test_merges"] += "#[tm.number] at [tm.head_commit]\n"
bug_report_data["round_id"] = GLOB.round_id
/datum/tgui_bug_report_form/proc/external_link_prompt(client/user)
@@ -118,11 +122,11 @@ GLOBAL_LIST_EMPTY(bug_report_time)
## Additional details
- Author: [initial_key]
- Approved By: [approving_user]
- Round ID: [GLOB.round_id ? GLOB.round_id : "N/A"]
- Client BYOND Version: [user_byond_version]
- Server BYOND Version: [world.byond_version].[world.byond_build]
- Server commit: [local_commit]
- Active Test Merges: [test_merges ? test_merges : "None"]
- Round ID: [bug_report_data["round_id"] ? bug_report_data["round_id"] : "N/A"]
- Client BYOND Version: [bug_report_data["user_byond_version"]]
- Server BYOND Version: [bug_report_data["server_byond_version"]]
- Server commit: [bug_report_data["local_commit"]]
- Active Test Merges: [bug_report_data["test_merges"] ? bug_report_data["test_merges"] : "None"]
- Note: [bug_report_data["approver_note"] ? bug_report_data["approver_note"] : "None"]
"}
@@ -156,6 +160,19 @@ GLOBAL_LIST_EMPTY(bug_report_time)
request.prepare(RUSTLIBS_HTTP_METHOD_POST, url, json_encode(payload), headers)
request.begin_async()
// Report has been handled so we can remove it from the DB.
// If the request fails the user is prompted to open an issue on Github, so we consider it handled as well.
var/datum/db_query/query_delete_bug_report = SSdbcore.NewQuery(
"DELETE FROM bug_reports WHERE (db_uid=:db_uid AND author_ckey=:author_ckey)",
list(
"db_uid" = db_uid,
"author_ckey" = initial_key,
)
)
query_delete_bug_report.warn_execute()
qdel(query_delete_bug_report)
var/start_time = world.time
UNTIL(request.is_complete() || (world.time > start_time + 5 SECONDS))
if(!request.is_complete() && world.time > start_time + 5 SECONDS)
@@ -168,14 +185,18 @@ GLOBAL_LIST_EMPTY(bug_report_time)
else
var/client/initial_user = locateUID(initial_user_uid)
message_admins("[user.ckey] has approved a bug report from [initial_key] titled [bug_report_data["title"]] at [time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")].")
to_chat(initial_user, "<span class='notice'>An admin has successfully submitted your report and it should now be visible on GitHub. Thanks again!</span>")
qdel(src)// approved and submitted, we no longer need the datum.
if(initial_user)
to_chat(initial_user, "<span class='notice'>An admin has successfully submitted your report and it should now be visible on GitHub. Thanks again!</span>")
// approved and submitted, we no longer need the datum.
qdel(src)
// proc that creates a ticket for an admin to approve or deny a bug report request
/datum/tgui_bug_report_form/proc/bug_report_request()
var/client/initial_user = locateUID(initial_user_uid)
if(initial_user)
to_chat(initial_user, "<span class='notice'>Your bug report has been submitted, thank you!</span>")
if(!db_uid)
db_uid = world.realtime
GLOB.bug_reports += src
var/general_message = "[initial_key] has created a bug report which is now pending approval. The report can be viewed using \"View Bug Reports\" in the debug tab. </span>"
@@ -192,6 +213,7 @@ GLOBAL_LIST_EMPTY(bug_report_time)
to_chat(user, "<span class='warning'>You have already approved this submission, please wait a moment for the API to process your submission.</span>")
return
bug_report_data = sanitize_payload(params)
add_metadata(user)
selected_confirm = TRUE
// bug report request is now waiting for admin approval
if(!awaiting_approval)
@@ -218,5 +240,15 @@ GLOBAL_LIST_EMPTY(bug_report_time)
var/client/initial_user = locateUID(initial_user_uid)
if(initial_user)
to_chat(initial_user, "<span class = 'warning'>A staff member has rejected your bug report, this can happen for several reasons. They will most likely get back to you shortly regarding your issue.</span>")
// Report has been handled so we can remove it from the DB
var/datum/db_query/query_delete_bug_report = SSdbcore.NewQuery(
"DELETE FROM bug_reports WHERE (db_uid=:db_uid AND author_ckey=:author_ckey)",
list(
"db_uid" = db_uid,
"author_ckey" = initial_key,
)
)
query_delete_bug_report.warn_execute()
qdel(query_delete_bug_report)
#undef STATUS_SUCCESS
+1 -1
View File
@@ -937,7 +937,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
for(var/datum/tgui_bug_report_form/report in GLOB.bug_reports)
bug_report_selection["[report.initial_key] - [report.bug_report_data["title"]]"] = report
var/datum/tgui_bug_report_form/form = bug_report_selection[tgui_input_list(usr, "Select a report to view:", "Bug Reports", bug_report_selection)]
if(!form.assign_approver(usr))
if(!form?.assign_approver(usr))
return
form.ui_interact(usr)
return