class_name BugbotServerBugzillaAPI extends "res://addons/Bugbot/Scripts/server_api.gd" ##region Consts const DEFAULT_SERVER : StringName = &"https://bugzilla.example.com" const DEFAULT_REST_URI : StringName = &"rest/" const DEFAULT_PRODUCT_NAME : StringName = &"ProductName" const DEFAULT_API_KEY : StringName = &"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn" const DEFAULT_POST_STATUS : StringName = &"UNCONFIRMED" const DEFAULT_MAP_NAME_FIELD : StringName = &"cf_mapname" const DEFAULT_MARKER_LOCATION_FIELD : StringName = &"cf_location" const DEFAULT_UNRESOLVED_STATUSES : Array = [&"UNCONFIRMED", &"CONFIRMED"] const DEFAULT_IN_PROGRESS_STATUSES : Array = [&"IN_PROGRESS"] const DEFAULT_RESOLVED_STATUSES : Array = [&"RESOLVED", &"VERIFIED"] #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/bugzilla/server", DEFAULT_SERVER)) != HTTPClient.STATUS_CONNECTED: printerr("Could not connect to server.") return var show_unresolved : bool = ProjectSettings.get_setting("bugbot/markers/unresolved/show_unresolved_bugs", DEFAULT_SHOW_UNRESOLVED_BUGS) var show_in_progress : bool = ProjectSettings.get_setting("bugbot/markers/in_progress/show_in_progress_bugs", DEFAULT_SHOW_IN_PROGRESS_BUGS) var show_resolved : bool = ProjectSettings.get_setting("bugbot/markers/resolved/show_resolved_bugs", DEFAULT_SHOW_RESOLVED_BUGS) var unresolved_labels : Array = ProjectSettings.get_setting("bugbot/reporting/bugzilla/status_labels/unresolved_statuses", DEFAULT_UNRESOLVED_STATUSES) var in_progress_labels : Array = ProjectSettings.get_setting("bugbot/reporting/bugzilla/status_labels/in_progress_statuses", DEFAULT_IN_PROGRESS_STATUSES) var resolved_labels : Array = ProjectSettings.get_setting("bugbot/reporting/bugzilla/status_labels/resolved_statuses", DEFAULT_RESOLVED_STATUSES) var map_name_field : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/map_name_field", DEFAULT_MAP_NAME_FIELD) var marker_location_field : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/marker_location_field", DEFAULT_MARKER_LOCATION_FIELD) var api_key : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/API_key", DEFAULT_API_KEY) var api_url : String = __build_url_string("bug?") api_url += map_name_field + "=" + map_name if show_unresolved: for unresolved_status in unresolved_labels: api_url += "&status=" + unresolved_status if show_in_progress: for in_progress_status in in_progress_labels: api_url += "&status=" + in_progress_status if show_resolved: for resolved_status in resolved_labels: api_url += "&status=" + resolved_status api_url += "&api_key=" + api_key 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": show_unresolved, "show_in_progress": show_in_progress, "show_resolved": show_resolved, "unresolved_labels": unresolved_labels, "in_progress_labels": in_progress_labels, "resolved_labels": resolved_labels, "map_name_field": map_name_field, "marker_location_field": marker_location_field, } for bug_in:Dictionary in response_data["bugs"]: var bug : BugbotBugData = __create_bug_data_from_server_response(bug_in, map_name, label_dict, true) 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/bugzilla/server", DEFAULT_SERVER)) != HTTPClient.STATUS_CONNECTED: printerr("Could not connect to server.") return var header_data : Array = __create_header_data() var product_name : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/product_name", DEFAULT_PRODUCT_NAME) var api_key : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/API_key", DEFAULT_API_KEY) var api_url : String = "product/%s?" % [product_name] api_url += "api_key=" + api_key var error : int = http_client.request(HTTPClient.METHOD_GET, __build_url_string(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 product_response_string : String = __get_http_client_chunk_response(http_client) var product_response : Dictionary = JSON.parse_string(product_response_string) if __validate_server_response(product_response) != Error.OK: return api_url = "field/bug?" api_url += "api_key=" + api_key error = http_client.request(HTTPClient.METHOD_GET, __build_url_string(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 fields_response_string : String = __get_http_client_chunk_response(http_client) var fields_response : Dictionary = JSON.parse_string(fields_response_string) if __validate_server_response(fields_response) != Error.OK: return var tag_lists : Array tag_lists.resize(BugbotTagArray.MAX) tag_lists[BugbotTagArray.VERSION] = [] tag_lists[BugbotTagArray.HARDWARE] = [] tag_lists[BugbotTagArray.OS] = [] tag_lists[BugbotTagArray.COMPONENT] = [] tag_lists[BugbotTagArray.SEVERITY] = [] var product_info : Dictionary = product_response["products"][0] if product_info["name"] != product_name: printerr("Incorrect product found.") __bugbot_server_thread.call_deferred("wait_to_finish") return for component:Dictionary in product_info["components"]: tag_lists[BugbotTagArray.COMPONENT].append({ "name": component["name"], "id": component["sort_key"] }) for version:Dictionary in product_info["versions"]: tag_lists[BugbotTagArray.VERSION].append({ "name": version["name"], "id": version["sort_key"] }) for field:Dictionary in fields_response["fields"]: if field.has("values"): var field_name : String = field["name"] var field_values : Array = field["values"] if field_name == "bug_severity": for severity:Dictionary in field_values: tag_lists[BugbotTagArray.SEVERITY].append({ "name": severity["name"], "id": severity["sort_key"] }) elif field_name == "rep_platform": for platform:Dictionary in field_values: tag_lists[BugbotTagArray.HARDWARE].append({ "name": platform["name"], "id": platform["sort_key"] }) elif field_name == "op_sys": for os:Dictionary in field_values: tag_lists[BugbotTagArray.OS].append({ "name": os["name"], "id": os["sort_key"] }) 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/bugzilla/server", DEFAULT_SERVER)) != HTTPClient.STATUS_CONNECTED: printerr("Could not connect to server.") return var product_name : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/product_name", DEFAULT_PRODUCT_NAME) var api_key : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/API_key", DEFAULT_API_KEY) var default_post_status : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/default_status", DEFAULT_POST_STATUS) var map_name_field : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/map_name_field", DEFAULT_MAP_NAME_FIELD) var marker_location_field : String = ProjectSettings.get_setting("bugbot/reporting/bugzilla/marker_location_field", DEFAULT_MARKER_LOCATION_FIELD) var marker_location : String = "%.4f,%.4f,%.4f:%.4f,%.4f,%.4f" % [bug_position.x, bug_position.y, bug_position.z, bug_normal.x, bug_normal.y, bug_normal.z] var api_url : String = "bug?" api_url += "api_key=" + api_key var post_data : Dictionary = { "product": product_name, map_name_field: map_name, marker_location_field: marker_location, "summary": data["title"], "description": data["body"], "status": default_post_status, } if not data["labels"]["version"].is_empty(): post_data["version"] = data["labels"]["version"]["name"] if not data["labels"]["hardware"].is_empty(): post_data["platform"] = data["labels"]["hardware"]["name"] if not data["labels"]["os"].is_empty(): post_data["op_sys"] = data["labels"]["os"]["name"] if not data["labels"]["component"].is_empty(): post_data["component"] = data["labels"]["component"]["name"] if not data["labels"]["severity"].is_empty(): post_data["severity"] = data["labels"]["severity"]["name"] var post_data_string : String = JSON.stringify(post_data) var header_data : Array = __create_header_data(post_data_string.length()) var error : int = http_client.request(HTTPClient.METHOD_POST, __build_url_string(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_string : String = __get_http_client_chunk_response(http_client) var post_response : Dictionary = JSON.parse_string(post_response_string) if __validate_server_response(post_response) != Error.OK: return post_data["id"] = post_response["id"] post_data["is_open"] = true var label_dict : Dictionary = { "show_unresolved": ProjectSettings.get_setting("bugbot/markers/unresolved/show_unresolved_bugs", DEFAULT_SHOW_UNRESOLVED_BUGS), "show_in_progress": ProjectSettings.get_setting("bugbot/markers/in_progress/show_in_progress_bugs", DEFAULT_SHOW_IN_PROGRESS_BUGS), "show_resolved": ProjectSettings.get_setting("bugbot/markers/resolved/show_resolved_bugs", DEFAULT_SHOW_RESOLVED_BUGS), "unresolved_labels": ProjectSettings.get_setting("bugbot/reporting/bugzilla/status_labels/unresolved_statuses", DEFAULT_UNRESOLVED_STATUSES), "in_progress_labels": ProjectSettings.get_setting("bugbot/reporting/bugzilla/status_labels/in_progress_statuses", DEFAULT_IN_PROGRESS_STATUSES), "resolved_labels": ProjectSettings.get_setting("bugbot/reporting/bugzilla/status_labels/resolved_statuses", DEFAULT_RESOLVED_STATUSES), "map_name_field": map_name_field, "marker_location_field": marker_location_field, } var bug_data : BugbotBugData = __create_bug_data_from_server_response(post_data, map_name, label_dict, false) callback.call_deferred(bug_data) __bugbot_server_thread.call_deferred("wait_to_finish") func _current_server_api() -> String: return "Bugzilla" func __build_url_string(_api_suffix:String) -> String: return "/" + \ ProjectSettings.get_setting("bugbot/reporting/bugzilla/REST_URI", DEFAULT_REST_URI) + \ "/" + \ _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("error") and _response["error"] == true) or \ _response.has("message"): var error_data : BugbotErrorData = BugbotErrorData.new() error_data.message = _response["message"] 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) -> BugbotBugData: var bug : BugbotBugData = BugbotBugData.new() # Check if the map name is valid for the scene we're in. bug.map_name = bug_in[label_dict["map_name_field"]] if bug.map_name != map_name: return null var marker_location : String = bug_in[label_dict["marker_location_field"]] if not marker_location.is_empty(): var marker_location_parts : PackedStringArray = marker_location.split(":") if marker_location_parts.size() == 2: var marker_position : PackedStringArray = marker_location_parts[0].split(",") var marker_normal : PackedStringArray = marker_location_parts[1].split(",") if marker_position.size() == 3: bug.marker_position = Vector3(float(marker_position[0]), float(marker_position[1]), float(marker_position[2])) if marker_normal.size() == 3: bug.marker_normal = Vector3(float(marker_normal[0]), float(marker_normal[1]), float(marker_normal[2])) 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. bug.is_open = bug_in["is_open"] bug.status = bug_in["status"] if not bug.is_open: resolved_tag_found = true else: for label:String in label_dict["resolved_labels"] as Array: if bug.status == label: resolved_tag_found = true for label:String in label_dict["in_progress_labels"] as Array: if bug.status == label: in_progress_tag_found = true for label:String in label_dict["unresolved_labels"] as Array: if bug.status == 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["id"] bug.title = bug_in["summary"] bug.component = bug_in["component"] bug.platform = bug_in["platform"] bug.operating_system = bug_in["op_sys"] bug.severity = bug_in["severity"] return bug