Bugbot/Scripts/server_gitea_api.gd
Jamie Greunbaum d20e47b5dc Gitea form post method now takes empty descriptions into account.
If the description box is empty, new lines are not added between the last line
and the marker data. If empty description boxes are eventually made possible, this
will allow for that.
2024-06-09 16:49:03 -04:00

304 lines
16 KiB
GDScript

class_name BugbotServerGiteaAPI
extends "res://addons/Bugbot/Scripts/server_api.gd"
#region Consts
const DEFAULT_SERVER : StringName = &"https://gitea.example.com"
const DEFAULT_BASE_URL : StringName = &"api/v1/"
const DEFAULT_OWNER_NAME : StringName = &"ProjectOwner"
const DEFAULT_REPO_NAME : StringName = &"ProjectName"
const DEFAULT_API_KEY : StringName = &"0123456789abcdef0123456789abcdef01234567"
const DEFAULT_BUG_LABEL : StringName = &"Kind/Bug"
const DEFAULT_STATUS_LABEL : StringName = &"Status/Need More Info"
const DEFAULT_VERSION_LABEL_PREFIX : StringName = &"Version/"
const DEFAULT_HARDWARE_LABEL_PREFIX : StringName = &"Hardware/"
const DEFAULT_OS_LABEL_PREFIX : StringName = &"OS/"
const DEFAULT_COMPONENT_LABEL_PREFIX : StringName = &"Component/"
const DEFAULT_PRIORITY_LABEL_PREFIX : StringName = &"Priority/"
const DEFAULT_SHOW_ASSIGNED_AS_IN_PROGRESS : bool = true
const DEFAULT_UNRESOLVED_STATUSES : Array = []
const DEFAULT_IN_PROGRESS_STATUSES : Array = []
const DEFAULT_RESOLVED_STATUSES : Array = []
#endregion
func __return_list_of_bugs_thread(map_name:String, callback:Callable) -> void:
var http_client : HTTPClient = HTTPClient.new()
if __connect_to_server(http_client, ProjectSettings.get_setting("bugbot/reporting/gitea/server", DEFAULT_SERVER)) != HTTPClient.STATUS_CONNECTED:
printerr("Could not connect to server.")
return
var api_url : String = __build_url_string("issues")
api_url += "?state=all&labels=" + ProjectSettings.get_setting("bugbot/reporting/gitea/bug_label", DEFAULT_BUG_LABEL).uri_encode()
var header_data : Array = __create_header_data()
var error : int = http_client.request(HTTPClient.METHOD_GET, api_url, header_data)
assert(error == Error.OK)
while http_client.get_status() == HTTPClient.STATUS_REQUESTING:
http_client.poll()
assert(http_client.get_status() == HTTPClient.STATUS_BODY or http_client.get_status() == HTTPClient.STATUS_CONNECTED)
var response_string : String = __get_http_client_chunk_response(http_client)
var response_data := JSON.parse_string(response_string)
if __validate_server_response(response_data) != Error.OK:
return
var bug_array : Array
var label_dict : Dictionary = {
"show_unresolved": ProjectSettings.get_setting("bugbot/markers/unresolved/show_unresolved_bugs", BugbotServerAPI.DEFAULT_SHOW_UNRESOLVED_BUGS),
"show_in_progress": ProjectSettings.get_setting("bugbot/markers/in_progress/show_in_progress_bugs", BugbotServerAPI.DEFAULT_SHOW_IN_PROGRESS_BUGS),
"show_resolved": ProjectSettings.get_setting("bugbot/markers/resolved/show_resolved_bugs", BugbotServerAPI.DEFAULT_SHOW_RESOLVED_BUGS),
"unresolved_labels": ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/unresolved_statuses", DEFAULT_UNRESOLVED_STATUSES),
"in_progress_labels": ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/in_progress_statuses", DEFAULT_IN_PROGRESS_STATUSES),
"resolved_labels": ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/resolved_statuses", DEFAULT_RESOLVED_STATUSES),
}
for bug_in:Dictionary in response_data:
var assigned_as_in_progress : bool = ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/show_assigned_as_in_progress", DEFAULT_SHOW_ASSIGNED_AS_IN_PROGRESS)
var bug : BugbotBugData = __create_bug_data_from_server_response(bug_in, map_name, label_dict, true, assigned_as_in_progress)
if bug:
bug_array.append(bug)
callback.call_deferred(bug_array)
__bugbot_server_thread.call_deferred("wait_to_finish")
func __prepare_form_thread(callback:Callable) -> void:
var http_client : HTTPClient = HTTPClient.new()
if __connect_to_server(http_client, ProjectSettings.get_setting("bugbot/reporting/gitea/server", DEFAULT_SERVER)) != HTTPClient.STATUS_CONNECTED:
printerr("Could not connect to server.")
return
# Pull a list of issue labels so we can get the ones we care about.
var api_url : String = __build_url_string("labels")
var header_data : Array = __create_header_data()
var error : int = http_client.request(HTTPClient.METHOD_GET, api_url, header_data)
assert(error == Error.OK)
while http_client.get_status() == HTTPClient.STATUS_REQUESTING:
http_client.poll()
assert(http_client.get_status() == HTTPClient.STATUS_BODY or http_client.get_status() == HTTPClient.STATUS_CONNECTED)
var response_string : String = __get_http_client_chunk_response(http_client)
var response_data : Variant = JSON.parse_string(response_string)
if __validate_server_response(response_data) != Error.OK:
return
var tag_lists : Array
tag_lists.resize(BugbotTagArray.MAX)
tag_lists[BugbotTagArray.VERSION] = Array()
tag_lists[BugbotTagArray.HARDWARE] = Array()
tag_lists[BugbotTagArray.OS] = Array()
tag_lists[BugbotTagArray.COMPONENT] = Array()
tag_lists[BugbotTagArray.SEVERITY] = Array()
var priority_prefix : String = ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/priority_label_prefix", DEFAULT_PRIORITY_LABEL_PREFIX)
var version_prefix : String = ProjectSettings.get_setting("bugbot/reporting/gitea/optional_labels/version_label_prefix", DEFAULT_VERSION_LABEL_PREFIX)
var hardware_prefix : String = ProjectSettings.get_setting("bugbot/reporting/gitea/optional_labels/hardware_label_prefix", DEFAULT_HARDWARE_LABEL_PREFIX)
var os_prefix : String = ProjectSettings.get_setting("bugbot/reporting/gitea/optional_labels/os_label_prefix", DEFAULT_OS_LABEL_PREFIX)
var component_prefix : String = ProjectSettings.get_setting("bugbot/reporting/gitea/optional_labels/component_label_prefix", DEFAULT_COMPONENT_LABEL_PREFIX)
response_data.sort_custom(func(a,b):return a["id"] < b["id"])
for label_in:Dictionary in response_data:
if version_prefix and label_in["name"].begins_with(version_prefix):
label_in["name"] = (label_in["name"] as String).replace(version_prefix, "")
tag_lists[BugbotTagArray.VERSION].append(label_in)
if hardware_prefix and label_in["name"].begins_with(hardware_prefix):
label_in["name"] = (label_in["name"] as String).replace(hardware_prefix, "")
tag_lists[BugbotTagArray.HARDWARE].append(label_in)
if os_prefix and label_in["name"].begins_with(os_prefix):
label_in["name"] = (label_in["name"] as String).replace(os_prefix, "")
tag_lists[BugbotTagArray.OS].append(label_in)
if component_prefix and label_in["name"].begins_with(component_prefix):
label_in["name"] = (label_in["name"] as String).replace(component_prefix, "")
tag_lists[BugbotTagArray.COMPONENT].append(label_in)
if priority_prefix and label_in["name"].begins_with(priority_prefix):
label_in["name"] = (label_in["name"] as String).replace(priority_prefix, "")
tag_lists[BugbotTagArray.SEVERITY].append(label_in)
callback.call_deferred(tag_lists)
__bugbot_server_thread.call_deferred("wait_to_finish")
func __send_form_data_thread(data:Dictionary, map_name:String, bug_position:Vector3, bug_normal:Vector3, callback:Callable) -> void:
var http_client : HTTPClient = HTTPClient.new()
if __connect_to_server(http_client, ProjectSettings.get_setting("bugbot/reporting/gitea/server", DEFAULT_SERVER)) != HTTPClient.STATUS_CONNECTED:
printerr("Could not connect to server.")
return
# Get a list of available labels to apply to the issue
var api_url : String = __build_url_string("labels")
var header_data : Array = __create_header_data()
var error : int = http_client.request(HTTPClient.METHOD_GET, api_url, header_data)
assert(error == Error.OK)
while http_client.get_status() == HTTPClient.STATUS_REQUESTING:
http_client.poll()
assert(http_client.get_status() == HTTPClient.STATUS_BODY or http_client.get_status() == HTTPClient.STATUS_CONNECTED)
var response_data : Variant = JSON.parse_string(__get_http_client_chunk_response(http_client))
if __validate_server_response(response_data) != Error.OK:
return
# Collect the label IDs for each label we want to apply to this issue
var labels : Array
var bug_label : String = ProjectSettings.get_setting("bugbot/reporting/gitea/bug_label", DEFAULT_BUG_LABEL)
var status_label : String = ProjectSettings.get_setting("bugbot/reporting/gitea/default_status_label", DEFAULT_STATUS_LABEL)
for label_in:Dictionary in response_data:
if label_in["name"] == bug_label or label_in["name"] == status_label:
labels.append(label_in["id"])
if data["labels"].has("version"): labels.append(data["labels"]["version"]["id"])
if data["labels"].has("hardware"): labels.append(data["labels"]["hardware"]["id"])
if data["labels"].has("os"): labels.append(data["labels"]["os"]["id"])
if data["labels"].has("component"): labels.append(data["labels"]["component"]["id"])
if data["labels"].has("severity"): labels.append(data["labels"]["severity"]["id"])
var marker_data : Dictionary = {
"map_name": map_name,
"bug_position": [bug_position.x, bug_position.y, bug_position.z],
"bug_normal": [bug_normal.x, bug_normal.y, bug_normal.z]
}
var post_data : Dictionary = {
"title": data["title"],
"body": data["body"],
"labels": labels
}
if not post_data["body"].is_empty(): post_data["body"] += "\n\n"
post_data["body"] += JSON.stringify(marker_data, "", false)
# Post issue to Gitea
api_url = __build_url_string("issues")
var post_data_string : String = JSON.stringify(post_data)
header_data = __create_header_data(post_data_string.length())
error = http_client.request(HTTPClient.METHOD_POST, api_url, header_data, post_data_string)
assert(error == Error.OK)
while http_client.get_status() == HTTPClient.STATUS_REQUESTING:
http_client.poll()
assert(http_client.get_status() == HTTPClient.STATUS_BODY or http_client.get_status() == HTTPClient.STATUS_CONNECTED)
var post_response_data : Variant = JSON.parse_string(__get_http_client_chunk_response(http_client))
if __validate_server_response(post_response_data) != Error.OK:
return
var label_dict : Dictionary = {
"show_unresolved": ProjectSettings.get_setting("bugbot/markers/unresolved/show_unresolved_bugs", true),
"show_in_progress": ProjectSettings.get_setting("bugbot/markers/in_progress/show_in_progress_bugs", true),
"show_resolved": ProjectSettings.get_setting("bugbot/markers/resolved/show_resolved_bugs", false),
"unresolved_labels": ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/unresolved_statuses", Array()),
"in_progress_labels": ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/in_progress_statuses", Array()),
"resolved_labels": ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/resolved_statuses", Array()),
}
var assigned_as_in_progress : bool = ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/show_assigned_as_in_progress", DEFAULT_SHOW_ASSIGNED_AS_IN_PROGRESS)
var bug_data : BugbotBugData = __create_bug_data_from_server_response(post_response_data, map_name, label_dict, false, assigned_as_in_progress)
callback.call_deferred(bug_data)
__bugbot_server_thread.call_deferred("wait_to_finish")
func _current_server_api() -> String:
return "Gitea"
func _get_project_name() -> String:
return ProjectSettings.get_setting("bugbot/reporting/gitea/repo_name", DEFAULT_REPO_NAME)
func _get_bug_url(bug_data:BugbotBugData) -> String:
var gitea_server : String = ProjectSettings.get_setting("bugbot/reporting/gitea/server", DEFAULT_SERVER)
var owner_name : String = ProjectSettings.get_setting("bugbot/reporting/gitea/owner_name", DEFAULT_OWNER_NAME)
var repo_name : String = ProjectSettings.get_setting("bugbot/reporting/gitea/repo_name", DEFAULT_REPO_NAME)
return "%s/%s/%s/issues/%d" % [gitea_server, owner_name, repo_name, bug_data.id]
func __build_url_string(_api_suffix:String) -> String:
return "/" + \
ProjectSettings.get_setting("bugbot/reporting/gitea/base_URL", DEFAULT_BASE_URL) + \
"/repos/" + \
ProjectSettings.get_setting("bugbot/reporting/gitea/owner_name", DEFAULT_OWNER_NAME) + \
"/" + \
ProjectSettings.get_setting("bugbot/reporting/gitea/repo_name", DEFAULT_REPO_NAME) + \
"/" + _api_suffix
func __create_header_data(content_length:int = -1) -> Array:
var header : Array = [
"User-Agent: Pirulo/1.0 (Godot)",
"Accept: application/json",
]
header.append("Authorization: token " + ProjectSettings.get_setting("bugbot/reporting/gitea/API_key", DEFAULT_API_KEY))
if content_length >= 0:
header.append("Content-Type: application/json")
header.append("Content-Length: " + String.num_uint64(content_length))
return header
func __validate_server_response(_response:Variant) -> int:
# If the response has a message field, make the assumption that this is
# because the response was an error code.
if _response.has("message"):
var error_data : BugbotErrorData = BugbotErrorData.new()
error_data.code = 1
error_data.message = _response["message"]
error_data.url = _response["url"]
printerr(error_data.message)
__bugbot_server_thread.call_deferred("wait_to_finish")
return Error.FAILED
return Error.OK
func __create_bug_data_from_server_response(bug_in:Dictionary, map_name:String, label_dict:Dictionary, validate_labels:bool, assigned_as_in_progress:bool) -> BugbotBugData:
var bugbot_marker_string : String = bug_in["body"].split("\n")[-1]
if not bugbot_marker_string.begins_with("{") and not bugbot_marker_string.ends_with("}"):
return null
var bug : BugbotBugData = BugbotBugData.new()
# Check if the map name is valid for the scene we're in.
var bugbot_marker_data : Dictionary = JSON.parse_string(bugbot_marker_string)
bug.map_name = bugbot_marker_data["map_name"]
if bug.map_name != map_name:
return null
var marker_location : Array = bugbot_marker_data["bug_position"]
var marker_normal : Array = bugbot_marker_data["bug_normal"]
bug.marker_position = Vector3(marker_location[0], marker_location[1], marker_location[2])
bug.marker_normal = Vector3(marker_normal[0], marker_normal[1], marker_normal[2])
var bug_labels : Array = []
var resolved_tag_found : bool = false
var in_progress_tag_found : bool = false
var unresolved_tag_found : bool = false
# Find which resolution statuses apply to this bug.
var priority_prefix : String = ProjectSettings.get_setting("bugbot/reporting/gitea/status_labels/priority_label_prefix", DEFAULT_PRIORITY_LABEL_PREFIX)
bug.is_open = (bug_in["state"] == "open")
if not bug.is_open:
resolved_tag_found = true
elif assigned_as_in_progress and bug_in["assignees"] is Array:
in_progress_tag_found = true
else:
for label:Dictionary in bug_in["labels"]:
bug_labels.append(label["name"] as String)
if priority_prefix and label["name"].begins_with(priority_prefix):
bug.severity = label["name"]
for label:String in label_dict["resolved_labels"] as Array:
if bug_labels.has(label): resolved_tag_found = true
for label:String in label_dict["in_progress_labels"] as Array:
if bug_labels.has(label): in_progress_tag_found = true
for label:String in label_dict["unresolved_labels"] as Array:
if bug_labels.has(label): unresolved_tag_found = true
# Figure out which resolution tag to prioritise, and whether we should show the marker.
var show_marker : bool
if resolved_tag_found:
bug.resolution = BugbotServerAPI.RESOLVED_TAG
show_marker = label_dict["show_resolved"] as bool
elif in_progress_tag_found:
bug.resolution = BugbotServerAPI.IN_PROGRESS_TAG
show_marker = label_dict["show_in_progress"] as bool
elif unresolved_tag_found or (label_dict["unresolved_labels"] as Array).is_empty():
bug.resolution = BugbotServerAPI.UNRESOLVED_TAG
show_marker = label_dict["show_unresolved"] as bool
if validate_labels and not show_marker:
return null
bug.id = bug_in["number"]
bug.key = String.num_int64(bug.id)
bug.title = bug_in["title"]
bug.body = bug_in["body"]
bug.component = &"Unused"
bug.platform = &"Unused"
bug.operating_system = &"Unused"
bug.status = bug_in["state"]
return bug