mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-04-04 03:22:32 +01:00
* initial work on migration to DB * another step * update report on submission * syntax corrections * syntax fix * uses 2 bits to record rejections differently and makes the verb only create one bug report datum * uses the correct value for the index this time * fixes reading a specific bug report * updates sql version * oops * changes the update file * Update 71-72.sql * adds detail to the comment in the update file * remakes the bug report table instead of altering it * change time2text for current time to SQL time * selects columns explicitly * change sql version in the code * renamed load_to_db to save_to_db for clarity * schema formatting and rounds can no longer be null * messages all relevant roles when a bug report is made and also * Prevents bug reports from being modified or handled by more than one person at a time * Update tgui.bundle.js * uses span macros in bug_report * replaces usr with client var in bug report view proc * Update debug.dm * bundle rebuild, rest of reviews --------- Co-authored-by: Migratingcocofruit <guycxz@gmail.com>
332 lines
14 KiB
Plaintext
332 lines
14 KiB
Plaintext
GLOBAL_LIST_EMPTY(bug_reports)
|
||
GLOBAL_LIST_EMPTY(bug_report_time)
|
||
|
||
// Datum for handling bug reports
|
||
#define STATUS_SUCCESS 201
|
||
|
||
/datum/tgui_bug_report_form
|
||
/// contains all the body text for the bug report.
|
||
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
|
||
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.
|
||
var/approving_user = null
|
||
|
||
/// value to determine if the bug report is submitted and awaiting admin/dev approval, used for state purposes in tgui.
|
||
var/awaiting_approval = FALSE
|
||
|
||
/// for garbage collection purposes.
|
||
var/selected_confirm = FALSE
|
||
|
||
/// time the report was filed
|
||
var/file_time
|
||
|
||
/// Index of the report in the db
|
||
var/row_index
|
||
|
||
/// Whether the report has been handled
|
||
var/handled = FALSE
|
||
|
||
/datum/tgui_bug_report_form/New(mob/user)
|
||
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)
|
||
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)
|
||
tgui_alert(user, "Unable to create a bug report at this time, please create the issue directly through our GitHub repository instead")
|
||
var/url = "https://github.com/ParadiseSS13/Paradise"
|
||
|
||
if(tgui_alert(user, "This will open the GitHub in your browser. Are you sure?", "Confirm", list("Yes", "No")) == "Yes")
|
||
user << link(url)
|
||
|
||
/datum/tgui_bug_report_form/ui_state()
|
||
return GLOB.always_state
|
||
|
||
/datum/tgui_bug_report_form/ui_interact(mob/user, datum/tgui/ui)
|
||
ui = SStgui.try_update_ui(user, src, ui)
|
||
if(!ui)
|
||
ui = new(user, src, "BugReportForm")
|
||
ui.open()
|
||
|
||
/datum/tgui_bug_report_form/ui_close(mob/user)
|
||
. = ..()
|
||
// handled reports can no longer be modified.
|
||
if(!handled)
|
||
var/datum/db_query/query_set_closed = SSdbcore.NewQuery("UPDATE bug_reports SET approver_ckey=\"\" WHERE id=:index", list("index" = row_index))
|
||
if(!query_set_closed.warn_execute())
|
||
to_chat(usr,chat_box_red("The bug report's state in row [row_index] could not be set to unopened"))
|
||
// The reports shouldn't persist unless being made or reviewed.
|
||
qdel(src)
|
||
|
||
/datum/tgui_bug_report_form/proc/sanitize_payload(list/params)
|
||
for(var/param in params)
|
||
params[param] = html_decode(sanitize(params[param], list("\t"=" ","<22>"=" ","<"=" ",">"=" ","&"=" ")))
|
||
|
||
return params
|
||
|
||
// whether or not an admin/dev can access the record at a given time.
|
||
/datum/tgui_bug_report_form/proc/assign_approver(mob/user)
|
||
if(!check_rights(R_VIEWRUNTIMES|R_ADMIN|R_DEBUG, user = user))
|
||
message_admins("[user.ckey] has attempted to review [initial_key]'s bug report titled [bug_report_data["title"]] without proper authorization at [SQLtime()].")
|
||
return FALSE
|
||
|
||
if(!initial_key)
|
||
to_chat(user, SPAN_WARNING("Unable to identify the author of the bug report."))
|
||
return FALSE
|
||
|
||
if(approving_user)
|
||
if(user.ckey == approving_user)
|
||
to_chat(user, SPAN_WARNING("This bug report review is already opened and accessed by you."))
|
||
return FALSE
|
||
else
|
||
to_chat(user, SPAN_WARNING("[approving_user] is currently accessing this report, it will open in read only mode."))
|
||
return TRUE
|
||
|
||
approving_user = user.ckey
|
||
var/datum/db_query/query_update_approver = SSdbcore.NewQuery("UPDATE bug_reports SET approver_ckey=:approver WHERE id=:index", list("approver" = user.ckey, "index" = row_index))
|
||
if(!query_update_approver.warn_execute())
|
||
to_chat(user, SPAN_WARNING("Failed to set bug report approver in row [row_index] in the database"))
|
||
return TRUE
|
||
|
||
// returns the body payload
|
||
/datum/tgui_bug_report_form/proc/create_form()
|
||
var/desc = {"
|
||
## What did you expect to happen?
|
||
[bug_report_data["expected_behavior"]]
|
||
|
||
## What happened instead?
|
||
[bug_report_data["description"]]
|
||
|
||
## Why is this bad/What are the consequences?
|
||
[bug_report_data["consequences"]]
|
||
|
||
## Steps to reproduce the issue:
|
||
[bug_report_data["steps"]]
|
||
|
||
## Attached logs
|
||
```
|
||
[bug_report_data["log"] ? bug_report_data["log"] : "N/A"]
|
||
```
|
||
|
||
## Additional details
|
||
- Author: [initial_key]
|
||
- Approved By: [approving_user]
|
||
- 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"] ? "\n[bug_report_data["test_merges"]]" : "None"]
|
||
- Note: [bug_report_data["approver_note"] ? bug_report_data["approver_note"] : "None"]
|
||
"}
|
||
|
||
return desc
|
||
|
||
// the real deal, we are sending the request through the api.
|
||
/datum/tgui_bug_report_form/proc/send_request(payload_body, client/user)
|
||
// for any future changes see https://docs.github.com/en/rest/issues/issues
|
||
var/repo_name = "Paradise"
|
||
var/org = "ParadiseSS13"
|
||
var/token = GLOB.configuration.system.github_api_token
|
||
|
||
if(token == null)
|
||
tgui_alert(user, "The configuration is not set for the external API.", "Issue not reported!")
|
||
external_link_prompt(user)
|
||
return
|
||
|
||
var/url = "https://api.github.com/repos/[org]/[repo_name]/issues"
|
||
var/list/headers = list()
|
||
headers["Authorization"] = "Bearer [token]"
|
||
headers["Content-Type"] = "text/markdown; charset=utf-8"
|
||
headers["Accept"] = "application/vnd.github+json"
|
||
|
||
var/datum/http_request/request = new()
|
||
var/list/payload = list(
|
||
"title" = bug_report_data["title"],
|
||
"body" = payload_body,
|
||
"labels" = list("Bug")
|
||
)
|
||
|
||
request.prepare(RUSTLIBS_HTTP_METHOD_POST, url, json_encode(payload), headers)
|
||
request.begin_async()
|
||
|
||
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)
|
||
CRASH("bug report HTML request hit timeout limit of 5 seconds");
|
||
|
||
var/datum/http_response/response = request.into_response()
|
||
if(response.errored || response.status_code != STATUS_SUCCESS)
|
||
message_admins(SPAN_ADMINNOTICE("The GitHub API has failed to create the bug report titled [bug_report_data["title"]] approved by [approving_user], status code:[response.status_code]. Please paste this error code into the development channel on discord."))
|
||
external_link_prompt(user)
|
||
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 [SQLtime()].")
|
||
if(initial_user)
|
||
to_chat(initial_user, SPAN_NOTICE("An admin has successfully submitted your report and it should now be visible on GitHub. Thanks again!"))
|
||
// Update bug report status on the DB
|
||
var/datum/db_query/query_update_submission = SSdbcore.NewQuery("UPDATE bug_reports SET submitted=1 WHERE id=:index", list("index" = row_index))
|
||
if(!query_update_submission.warn_execute())
|
||
message_admins("Failed to update status of bug report from [initial_key] titled [bug_report_data["title"]] to submitted at [SQLtime()]. DB request failed")
|
||
handled = TRUE
|
||
|
||
// 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)
|
||
file_time = SQLtime()
|
||
if(!save_to_db())
|
||
external_link_prompt(initial_user)
|
||
return
|
||
if(initial_user)
|
||
to_chat(initial_user, SPAN_NOTICE("Your bug report has been submitted, thank you!"))
|
||
message_roles(chat_box_green("[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"), R_DEBUG|R_VIEWRUNTIMES|R_ADMIN)
|
||
|
||
/datum/tgui_bug_report_form/ui_act(action, list/params, datum/tgui/ui)
|
||
. = ..()
|
||
if(.)
|
||
return
|
||
if(handled)
|
||
to_chat(ui.user, SPAN_WARNING("The report has already been handled. Changes cannot be made."))
|
||
return
|
||
if(approving_user && approving_user != ui.user.ckey)
|
||
to_chat(ui.user, SPAN_WARNING("The report is in use by someone else and has been opened in read only mode. Changes cannot be made."))
|
||
return
|
||
var/mob/user = ui.user
|
||
switch(action)
|
||
if("confirm")
|
||
if(selected_confirm) // prevent someone from spamming the approve button
|
||
to_chat(user, SPAN_WARNING("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 being submitted for approval
|
||
if(!awaiting_approval)
|
||
bug_report_request()
|
||
GLOB.bug_report_time[user.ckey] = world.time
|
||
awaiting_approval = TRUE
|
||
// Otherwise it's being approved
|
||
else
|
||
var/payload_body = create_form()
|
||
send_request(payload_body, user.client)
|
||
if("cancel")
|
||
if(awaiting_approval) // admin has chosen to reject the bug report
|
||
reject(user.client)
|
||
ui.close()
|
||
. = TRUE
|
||
|
||
/datum/tgui_bug_report_form/ui_data(mob/user)
|
||
. = list()
|
||
.["read_only"] = approving_user ? approving_user != user.ckey : FALSE
|
||
.["report_details"] = bug_report_data // only filled out once the user as submitted the form
|
||
.["awaiting_approval"] = awaiting_approval
|
||
|
||
/datum/tgui_bug_report_form/proc/reject(client/user)
|
||
var/datum/db_query/query_update_submission = SSdbcore.NewQuery("UPDATE bug_reports SET submitted=2 WHERE id=:index", list("index" = row_index))
|
||
if(!query_update_submission.warn_execute())
|
||
message_admins("Failed to reject bug report from [initial_key] titled [bug_report_data["title"]] at [SQLtime()]. DB request failed")
|
||
else
|
||
handled = TRUE
|
||
message_admins("[user.ckey] has rejected a bug report from [initial_key] titled [bug_report_data["title"]] at [SQLtime()].")
|
||
var/client/initial_user = locateUID(initial_user_uid)
|
||
if(initial_user)
|
||
to_chat(initial_user, SPAN_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."))
|
||
qdel(query_update_submission)
|
||
|
||
/// Populates a list using the bug reports db table and returns it
|
||
/proc/read_bug_report_table()
|
||
var/list/bug_reports = list()
|
||
var/datum/db_query/query_bug_reports = SSdbcore.NewQuery("SELECT id, filetime, author_ckey, contents_json, approver_ckey, submitted FROM bug_reports WHERE submitted=0")
|
||
if(!query_bug_reports.warn_execute())
|
||
log_debug("Failed to load stored bug reports from DB")
|
||
qdel(query_bug_reports)
|
||
return list()
|
||
while(query_bug_reports.NextRow())
|
||
var/datum/tgui_bug_report_form/bug_report = new()
|
||
bug_reports += bug_report
|
||
bug_report.row_index = query_bug_reports.item[1]
|
||
bug_report.file_time = query_bug_reports.item[2]
|
||
bug_report.initial_key = query_bug_reports.item[3]
|
||
bug_report.bug_report_data = json_decode(query_bug_reports.item[4])
|
||
bug_report.approving_user = query_bug_reports.item[5] == "" ? null : query_bug_reports.item[5]
|
||
bug_report.awaiting_approval = TRUE
|
||
bug_report.handled = !!query_bug_reports.item[6]
|
||
qdel(query_bug_reports)
|
||
|
||
return bug_reports
|
||
|
||
/// Create a list of ids, authors and titles of bug reports awaiting approval
|
||
/proc/read_bug_report_titles()
|
||
var/list/bug_reports = list()
|
||
var/datum/db_query/query_bug_reports = SSdbcore.NewQuery("SELECT id, author_ckey, contents_json FROM bug_reports WHERE submitted=0")
|
||
if(!query_bug_reports.warn_execute())
|
||
log_debug("Failed to load stored bug reports from DB")
|
||
qdel(query_bug_reports)
|
||
return list()
|
||
while(query_bug_reports.NextRow())
|
||
var/list/report_data = json_decode(query_bug_reports.item[3])
|
||
bug_reports["[query_bug_reports.item[2]] - [report_data["title"]]"] = query_bug_reports.item[1]
|
||
qdel(query_bug_reports)
|
||
|
||
return bug_reports
|
||
|
||
/proc/read_bug_report(index)
|
||
var/datum/db_query/query_bug_reports = SSdbcore.NewQuery("SELECT id, filetime, author_ckey, contents_json, approver_ckey, submitted FROM bug_reports WHERE id=:index", list("index" = index))
|
||
if(!query_bug_reports.warn_execute())
|
||
log_debug("Failed to load bug report from DB")
|
||
qdel(query_bug_reports)
|
||
return
|
||
if(!query_bug_reports.NextRow())
|
||
log_debug("Attempted to load bug report from DB with invalid index")
|
||
qdel(query_bug_reports)
|
||
return
|
||
var/datum/tgui_bug_report_form/bug_report = new()
|
||
bug_report.row_index = query_bug_reports.item[1]
|
||
bug_report.file_time = query_bug_reports.item[2]
|
||
bug_report.initial_key = query_bug_reports.item[3]
|
||
bug_report.bug_report_data = json_decode(query_bug_reports.item[4])
|
||
bug_report.approving_user = query_bug_reports.item[5] == "" ? null : query_bug_reports.item[5]
|
||
bug_report.awaiting_approval = TRUE
|
||
bug_report.handled = !!query_bug_reports.item[6]
|
||
qdel(query_bug_reports)
|
||
return bug_report
|
||
|
||
/datum/tgui_bug_report_form/proc/save_to_db()
|
||
. = TRUE
|
||
var/datum/db_query/bug_query = SSdbcore.NewQuery({"
|
||
INSERT INTO bug_reports (filetime, author_ckey, title, round_id, contents_json, approver_ckey) VALUES (:filetime, :author_ckey, :title, :round_id, :contents_json, :approver_ckey)
|
||
"},
|
||
list(
|
||
"filetime" = file_time,
|
||
"author_ckey" = initial_key,
|
||
"title" = bug_report_data["title"],
|
||
"round_id" = bug_report_data["round_id"],
|
||
"contents_json" = json_encode(bug_report_data),
|
||
"approver_ckey" = approving_user ? approving_user : ""
|
||
)
|
||
)
|
||
if(!bug_query.warn_execute())
|
||
. = FALSE
|
||
qdel(bug_query)
|
||
|
||
#undef STATUS_SUCCESS
|