From 1b2883e92e8583fbf476a74621f9bdfc8532065f Mon Sep 17 00:00:00 2001 From: Migratingcocofruit <69551563+Migratingcocofruit@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:06:34 +0200 Subject: [PATCH] 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 --- SQL/paradise_schema.sql | 14 ++++ SQL/updates/70-71.sql | 11 +++ code/__DEFINES/misc_defines.dm | 2 +- code/controllers/subsystem/SSbugreports.dm | 40 +++++++++++ code/datums/bug_report.dm | 80 +++++++++++++++------- code/modules/admin/verbs/debug.dm | 2 +- config/example/config.toml | 2 +- paradise.dme | 1 + 8 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 SQL/updates/70-71.sql create mode 100644 code/controllers/subsystem/SSbugreports.dm diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql index 3c16d6ae650..8bab7b207f7 100644 --- a/SQL/paradise_schema.sql +++ b/SQL/paradise_schema.sql @@ -666,3 +666,17 @@ CREATE TABLE `json_datum_saves` ( UNIQUE INDEX `ckey_unique` (`ckey`, `slotname`) USING BTREE, INDEX `ckey` (`ckey`) USING BTREE ) COLLATE = 'utf8mb4_general_ci' ENGINE = InnoDB; + +-- +-- Table structure for table 'bug_reports' +-- +DROP TABLE IF EXISTS `bug_reports`; +CREATE TABLE `bug_reports` ( + `db_uid` BIGINT(32) NOT NULL, + `author_ckey` varchar(32) NOT NULL, + `title` MEDIUMTEXT COLLATE 'utf8mb4_general_ci', + `round_id` int(11), + `contents_json` LONGTEXT, + CONSTRAINT bug_key PRIMARY KEY (`db_uid`,`author_ckey`) USING BTREE + +) COLLATE = 'utf8mb4_general_ci' ENGINE = INNODB; diff --git a/SQL/updates/70-71.sql b/SQL/updates/70-71.sql new file mode 100644 index 00000000000..a2e704286ea --- /dev/null +++ b/SQL/updates/70-71.sql @@ -0,0 +1,11 @@ +# Updating SQL from 70 to 71 -MigratingCocofruit +# Adding new table for bug reports +CREATE TABLE `bug_reports` ( + `db_uid` INT(32) NOT NULL, + `author_ckey` varchar(32) NOT NULL, + `title` MEDIUMTEXT COLLATE 'utf8mb4_general_ci', + `round_id` int(11), + `contents_json` LONGTEXT, + CONSTRAINT bug_key PRIMARY KEY (`db_uid`,`author_ckey`) USING BTREE + +) COLLATE = 'utf8mb4_general_ci' ENGINE = INNODB; diff --git a/code/__DEFINES/misc_defines.dm b/code/__DEFINES/misc_defines.dm index 66c3218bd6d..0edeabbc864 100644 --- a/code/__DEFINES/misc_defines.dm +++ b/code/__DEFINES/misc_defines.dm @@ -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) diff --git a/code/controllers/subsystem/SSbugreports.dm b/code/controllers/subsystem/SSbugreports.dm new file mode 100644 index 00000000000..8d14496a94b --- /dev/null +++ b/code/controllers/subsystem/SSbugreports.dm @@ -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) diff --git a/code/datums/bug_report.dm b/code/datums/bug_report.dm index f2f7aa49549..8946a6e608f 100644 --- a/code/datums/bug_report.dm +++ b/code/datums/bug_report.dm @@ -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, "An admin has successfully submitted your report and it should now be visible on GitHub. Thanks again!") - qdel(src)// approved and submitted, we no longer need the datum. + if(initial_user) + to_chat(initial_user, "An admin has successfully submitted your report and it should now be visible on GitHub. Thanks again!") + // 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, "Your bug report has been submitted, thank you!") + 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. " @@ -192,6 +213,7 @@ GLOBAL_LIST_EMPTY(bug_report_time) to_chat(user, "You have already approved this submission, please wait a moment for the API to process your submission.") 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, "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.") + // 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 diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 9213fe72adb..1067496b9d1 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -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 diff --git a/config/example/config.toml b/config/example/config.toml index 43a484e58e9..598bee10690 100644 --- a/config/example/config.toml +++ b/config/example/config.toml @@ -180,7 +180,7 @@ fluff_undershirts = [ # Enable/disable the database on a whole sql_enabled = false # SQL version. If this is a mismatch, round start will be delayed -sql_version = 70 +sql_version = 71 # SQL server address. Can be an IP or DNS name sql_address = "127.0.0.1" # SQL server port diff --git a/paradise.dme b/paradise.dme index a29f112cf22..5b13b27782c 100644 --- a/paradise.dme +++ b/paradise.dme @@ -323,6 +323,7 @@ #include "code\controllers\subsystem\SSair.dm" #include "code\controllers\subsystem\SSambience.dm" #include "code\controllers\subsystem\SSblackbox.dm" +#include "code\controllers\subsystem\SSbugreports.dm" #include "code\controllers\subsystem\SScamera.dm" #include "code\controllers\subsystem\SSchat.dm" #include "code\controllers\subsystem\SScleanup.dm"