480 lines
16 KiB
C++
480 lines
16 KiB
C++
// ©2022 Batty Bovine Productions, LLC. All Rights Reserved.
|
|
|
|
#include "BugSubmissionForm.h"
|
|
|
|
#include "BugMarkerActor.h"
|
|
#include "CommonButtonBase.h"
|
|
#include "CommonTextBlock.h"
|
|
#include "HttpModule.h"
|
|
#include "JsonObjectConverter.h"
|
|
#include "UnrealzillaGlobalSettings.h"
|
|
|
|
#include "Components/CircularThrobber.h"
|
|
#include "Components/Overlay.h"
|
|
#include "Components/VerticalBox.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
|
|
|
|
const FText FormatFloatToText(float Float, int32 IntegralDigits, int32 FractionalDigits)
|
|
{
|
|
const float Rounded = FMath::RoundToInt(Float);
|
|
if(FMath::Abs(Float - Rounded) < FMath::Pow(10.0f, -1 * FractionalDigits))
|
|
{
|
|
Float = Rounded;
|
|
}
|
|
FNumberFormattingOptions NumberFormat;
|
|
NumberFormat.MinimumIntegralDigits = IntegralDigits;
|
|
NumberFormat.MaximumIntegralDigits = INT32_MAX;
|
|
NumberFormat.MinimumFractionalDigits = FractionalDigits;
|
|
NumberFormat.MaximumFractionalDigits = FractionalDigits;
|
|
NumberFormat.UseGrouping = false;
|
|
return FText::AsNumber(Float, &NumberFormat);
|
|
}
|
|
|
|
const FString FormatQueryString(const TMap<FString, FString> &QueryData)
|
|
{
|
|
TArray<FString> AssembledKeyValuePairs;
|
|
TArray<FString> QueryKeys;
|
|
QueryData.GenerateKeyArray(QueryKeys);
|
|
for (const FString &QueryKey : QueryKeys)
|
|
{
|
|
AssembledKeyValuePairs.Add(QueryKey + "=" + QueryData[QueryKey]);
|
|
}
|
|
|
|
return FString::Join(AssembledKeyValuePairs, TEXT("&"));
|
|
}
|
|
|
|
const FString GetGameVersion()
|
|
{
|
|
FString GameVersion;
|
|
GConfig->GetString(TEXT("/Script/EngineSettings.GeneralProjectSettings"), TEXT("ProjectVersion"), GameVersion, GGameIni);
|
|
if (GameVersion.IsEmpty())
|
|
{
|
|
GameVersion = "1.0.0.0";
|
|
}
|
|
return GameVersion;
|
|
}
|
|
|
|
|
|
void UBugSubmissionForm::NativeOnInitialized()
|
|
{
|
|
this->ShowProcessingOverlayLoading();
|
|
|
|
this->ProductNameValue->SetText(FText::AsCultureInvariant(GetDefault<UUnrealzillaGlobalSettings>()->ProductName));
|
|
|
|
const FString FullURL = GetDefault<UUnrealzillaGlobalSettings>()->SubmissionServer + "/rest.cgi";
|
|
|
|
// Assemble query data into key:value pairs
|
|
TMap<FString, FString> QueryData;
|
|
QueryData.Add("api_key", GetDefault<UUnrealzillaGlobalSettings>()->APIKey);
|
|
const FString QueryString = FormatQueryString(QueryData);
|
|
|
|
// Query the server for information about the current product
|
|
FHttpModule &HttpModule = FHttpModule::Get();
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> ProductRequest = HttpModule.CreateRequest();
|
|
ProductRequest->SetVerb(TEXT("GET"));
|
|
ProductRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
|
|
ProductRequest->SetURL(FullURL + "/product/" + GetDefault<UUnrealzillaGlobalSettings>()->ProductName + "?" + QueryString);
|
|
ProductRequest->OnProcessRequestComplete().BindUObject(this, &UBugSubmissionForm::ServerProductInfoResponse);
|
|
ProductRequest->ProcessRequest();
|
|
|
|
// Send a second query to retrieve severity options
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> SeverityRequest = HttpModule.CreateRequest();
|
|
SeverityRequest->SetVerb(TEXT("GET"));
|
|
SeverityRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
|
|
SeverityRequest->SetURL(FullURL + "/field/bug/bug_severity" + "?" + QueryString);
|
|
SeverityRequest->OnProcessRequestComplete().BindUObject(this, &UBugSubmissionForm::ServerSeverityInfoResponse);
|
|
SeverityRequest->ProcessRequest();
|
|
|
|
// Send a third query to retrieve platform options
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> PlatformsRequest = HttpModule.CreateRequest();
|
|
PlatformsRequest->SetVerb(TEXT("GET"));
|
|
PlatformsRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
|
|
PlatformsRequest->SetURL(FullURL + "/field/bug/rep_platform" + "?" + QueryString);
|
|
PlatformsRequest->OnProcessRequestComplete().BindUObject(this, &UBugSubmissionForm::ServerPlatformInfoResponse);
|
|
PlatformsRequest->ProcessRequest();
|
|
|
|
// Send a final query to retrieve OS options
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> OSRequest = HttpModule.CreateRequest();
|
|
OSRequest->SetVerb(TEXT("GET"));
|
|
OSRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
|
|
OSRequest->SetURL(FullURL + "/field/bug/op_sys" + "?" + QueryString);
|
|
OSRequest->OnProcessRequestComplete().BindUObject(this, &UBugSubmissionForm::ServerOSInfoResponse);
|
|
OSRequest->ProcessRequest();
|
|
|
|
this->SubmitButton->OnClicked().AddUObject(this, &UBugSubmissionForm::SubmitForm);
|
|
this->CancelButton->OnClicked().AddUObject(this, &UBugSubmissionForm::CancelForm);
|
|
}
|
|
|
|
|
|
void UBugSubmissionForm::SetMarkerData(ABugMarkerActor *BugMarker)
|
|
{
|
|
this->BugMarkerActor = BugMarker;
|
|
|
|
this->MapName = FText::AsCultureInvariant(this->GetWorld()->GetMapName().RightChop(this->GetWorld()->StreamingLevelsPrefix.Len()));
|
|
|
|
const FVector MarkerLocation = this->BugMarkerActor->GetActorLocation();
|
|
FFormatNamedArguments MarkerLocationArgs;
|
|
MarkerLocationArgs.Add("X", FormatFloatToText(MarkerLocation.X, 1, 4));
|
|
MarkerLocationArgs.Add("Y", FormatFloatToText(MarkerLocation.Y, 1, 4));
|
|
MarkerLocationArgs.Add("Z", FormatFloatToText(MarkerLocation.Z, 1, 4));
|
|
const FText MarkerLocationText = FText::Format(NSLOCTEXT("BugSubmissionForm", "MarkerLocation", "{X},{Y},{Z}"), MarkerLocationArgs);
|
|
|
|
const FVector MarkerUpVector = this->BugMarkerActor->GetActorUpVector();
|
|
FFormatNamedArguments MarkerUpVectorArgs;
|
|
MarkerUpVectorArgs.Add("X", FormatFloatToText(MarkerUpVector.X, 1, 4));
|
|
MarkerUpVectorArgs.Add("Y", FormatFloatToText(MarkerUpVector.Y, 1, 4));
|
|
MarkerUpVectorArgs.Add("Z", FormatFloatToText(MarkerUpVector.Z, 1, 4));
|
|
const FText MarkerUpVectorText = FText::Format(NSLOCTEXT("BugSubmissionForm", "MarkerUpVector", "{X},{Y},{Z}"), MarkerUpVectorArgs);
|
|
|
|
FFormatNamedArguments MapDataArgs;
|
|
MapDataArgs.Add("MarkerLocation", MarkerLocationText);
|
|
MapDataArgs.Add("MarkerUpVector", MarkerUpVectorText);
|
|
const FText MapDataText = FText::Format(NSLOCTEXT("BugSubmissionForm", "MapDataFormat", "{MarkerLocation}:{MarkerUpVector}"), MapDataArgs);
|
|
|
|
this->MarkerLocation = MapDataText;
|
|
}
|
|
|
|
|
|
void UBugSubmissionForm::ShowProcessingOverlayLoading()
|
|
{
|
|
this->ProcessingRequestErrorText->SetText(FText::AsCultureInvariant(""));
|
|
this->ProcessingRequestOverlay->SetVisibility(ESlateVisibility::Visible);
|
|
this->ProcessingRequestThrobber->SetVisibility(ESlateVisibility::Visible);
|
|
this->ProcessingRequestErrorBox->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
|
|
void UBugSubmissionForm::ShowProcessingOverlayMessage(const FString Message)
|
|
{
|
|
this->ProcessingRequestErrorText->SetText(FText::AsCultureInvariant(Message));
|
|
this->ProcessingRequestOverlay->SetVisibility(ESlateVisibility::Visible);
|
|
this->ProcessingRequestThrobber->SetVisibility(ESlateVisibility::Collapsed);
|
|
this->ProcessingRequestErrorBox->SetVisibility(ESlateVisibility::Visible);
|
|
}
|
|
void UBugSubmissionForm::HideProcessingOverlay()
|
|
{
|
|
this->ProcessingRequestOverlay->SetVisibility(ESlateVisibility::Collapsed);
|
|
}
|
|
|
|
|
|
void UBugSubmissionForm::SubmitForm()
|
|
{
|
|
this->ShowProcessingOverlayLoading();
|
|
|
|
const FString FullURL = GetDefault<UUnrealzillaGlobalSettings>()->SubmissionServer + "/rest.cgi";
|
|
const FString RequestURL = "/bug";
|
|
|
|
// Assemble query data into key:value pairs
|
|
TMap<FString, FString> QueryData;
|
|
QueryData.Add("api_key", GetDefault<UUnrealzillaGlobalSettings>()->APIKey);
|
|
|
|
const FString SummaryText = this->SummaryEntryBox->GetText().ToString();
|
|
const FString CommentText = this->CommentEntryBox->GetText().ToString();
|
|
|
|
const FString DefaultStatus = GetDefault<UUnrealzillaGlobalSettings>()->DefaultStatus;
|
|
|
|
FString PostJsonString;
|
|
FJSONPostBug PostData;
|
|
PostData.product = GetDefault<UUnrealzillaGlobalSettings>()->ProductName;
|
|
PostData.version = this->VersionButton->GetText().ToString();
|
|
PostData.platform = this->HardwareButton->GetText().ToString();
|
|
PostData.op_sys = this->OSButton->GetText().ToString();
|
|
PostData.component = this->ComponentButton->GetText().ToString();
|
|
PostData.severity = this->SeverityButton->GetText().ToString();
|
|
PostData.cf_mapname = this->MapName.ToString();
|
|
PostData.cf_location = this->MarkerLocation.ToString();
|
|
PostData.summary = SummaryText;
|
|
PostData.description = CommentText;
|
|
if (!DefaultStatus.IsEmpty())
|
|
{
|
|
PostData.status = DefaultStatus;
|
|
}
|
|
FJsonObjectConverter::UStructToJsonObjectString(PostData, PostJsonString);
|
|
|
|
if (PostData.version.IsEmpty())
|
|
{
|
|
this->ShowProcessingOverlayMessage("You must select a version number.");
|
|
return;
|
|
}
|
|
if (PostData.platform.IsEmpty() || PostData.op_sys.IsEmpty())
|
|
{
|
|
PostData.platform = "All";
|
|
PostData.op_sys = "All";
|
|
}
|
|
if (PostData.component.IsEmpty())
|
|
{
|
|
this->ShowProcessingOverlayMessage("You must select a component.");
|
|
return;
|
|
}
|
|
if (PostData.severity.IsEmpty())
|
|
{
|
|
this->ShowProcessingOverlayMessage("You must select a severity level.");
|
|
return;
|
|
}
|
|
if (SummaryText.IsEmpty())
|
|
{
|
|
this->ShowProcessingOverlayMessage("You must provide a summary.");
|
|
return;
|
|
}
|
|
|
|
FHttpModule &HttpModule = FHttpModule::Get();
|
|
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = HttpModule.CreateRequest();
|
|
Request->SetVerb(TEXT("POST"));
|
|
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
|
|
Request->SetURL(FullURL + RequestURL + "?" + FormatQueryString(QueryData));
|
|
Request->SetContentAsString(PostJsonString);
|
|
Request->OnProcessRequestComplete().BindUObject(this, &UBugSubmissionForm::ServerPOSTResponse);
|
|
Request->ProcessRequest();
|
|
|
|
this->OnFormSubmit.ExecuteIfBound();
|
|
}
|
|
|
|
void UBugSubmissionForm::CancelForm()
|
|
{
|
|
this->OnFormCancelled.ExecuteIfBound();
|
|
|
|
this->CloseForm();
|
|
|
|
this->BugMarkerActor->Destroy();
|
|
}
|
|
|
|
void UBugSubmissionForm::CloseForm()
|
|
{
|
|
UGameplayStatics::GetPlayerController(this, 0)->SetInputMode(FInputModeGameOnly());
|
|
UGameplayStatics::GetPlayerController(this, 0)->bShowMouseCursor = false;
|
|
this->RemoveFromParent();
|
|
}
|
|
|
|
|
|
void UBugSubmissionForm::ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
|
|
{
|
|
if (Success)
|
|
{
|
|
FJSONPostResponse ResponseData;
|
|
FString JSONResponse = Response->GetContentAsString();
|
|
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
|
|
|
|
if (ResponseData.error)
|
|
{
|
|
this->ShowProcessingOverlayMessage(ResponseData.message);
|
|
}
|
|
else
|
|
{
|
|
this->CloseForm();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->ServerConnectionError(Request->GetStatus());
|
|
}
|
|
}
|
|
|
|
void UBugSubmissionForm::ServerProductInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
|
|
{
|
|
if (Success)
|
|
{
|
|
FJSONProductResponse ResponseData;
|
|
FString JSONResponse = Response->GetContentAsString();
|
|
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
|
|
|
|
if (ResponseData.error)
|
|
{
|
|
this->ShowProcessingOverlayMessage(ResponseData.message);
|
|
}
|
|
else
|
|
{
|
|
if (!ResponseData.products.IsEmpty())
|
|
{
|
|
const FJSONProductData &ProductData = ResponseData.products[0];
|
|
if (ProductData.name == GetDefault<UUnrealzillaGlobalSettings>()->ProductName)
|
|
{
|
|
for (const FJSONComponentData &ComponentData : ProductData.components)
|
|
{
|
|
this->ComponentList.Add(ComponentData.name);
|
|
}
|
|
for (const FJSONVersionData &VersionData : ProductData.versions)
|
|
{
|
|
this->VersionsList.Add(VersionData.name);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this->VersionsList.Contains(GetGameVersion()))
|
|
{
|
|
this->VersionButton->SetText(FText::AsCultureInvariant(GetGameVersion()));
|
|
}
|
|
else if (this->VersionsList.Contains("unspecified"))
|
|
{
|
|
this->VersionButton->SetText(FText::AsCultureInvariant("unspecified"));
|
|
}
|
|
else if (this->VersionsList.Contains("Latest"))
|
|
{
|
|
this->VersionButton->SetText(FText::AsCultureInvariant("Latest"));
|
|
}
|
|
else if (!this->VersionsList.IsEmpty())
|
|
{
|
|
this->VersionButton->SetText(FText::AsCultureInvariant(this->VersionsList[0]));
|
|
}
|
|
|
|
this->CheckIfAllInitialResponsesAreIn();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->ServerConnectionError(Request->GetStatus());
|
|
}
|
|
}
|
|
|
|
void UBugSubmissionForm::ServerSeverityInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
|
|
{
|
|
if (Success)
|
|
{
|
|
FJSONFieldResponse ResponseData;
|
|
FString JSONResponse = Response->GetContentAsString();
|
|
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
|
|
|
|
if (ResponseData.error)
|
|
{
|
|
this->ShowProcessingOverlayMessage(ResponseData.message);
|
|
}
|
|
else
|
|
{
|
|
if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "bug_severity")
|
|
{
|
|
for (const FJSONFieldValueData &FieldValue : ResponseData.fields[0].values)
|
|
{
|
|
this->SeverityList.Add(FieldValue.name);
|
|
}
|
|
}
|
|
|
|
this->CheckIfAllInitialResponsesAreIn();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->ServerConnectionError(Request->GetStatus());
|
|
}
|
|
}
|
|
|
|
void UBugSubmissionForm::ServerPlatformInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
|
|
{
|
|
if (Success)
|
|
{
|
|
FJSONFieldResponse ResponseData;
|
|
FString JSONResponse = Response->GetContentAsString();
|
|
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
|
|
|
|
if (ResponseData.error)
|
|
{
|
|
this->ShowProcessingOverlayMessage(ResponseData.message);
|
|
}
|
|
else
|
|
{
|
|
if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "rep_platform")
|
|
{
|
|
for (const FJSONFieldValueData &FieldValue : ResponseData.fields[0].values)
|
|
{
|
|
this->PlatformsList.Add(FieldValue.name);
|
|
}
|
|
}
|
|
|
|
this->CheckIfAllInitialResponsesAreIn();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->ServerConnectionError(Request->GetStatus());
|
|
}
|
|
}
|
|
|
|
void UBugSubmissionForm::ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
|
|
{
|
|
if (Success)
|
|
{
|
|
FJSONFieldResponse ResponseData;
|
|
FString JSONResponse = Response->GetContentAsString();
|
|
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
|
|
|
|
if (ResponseData.error)
|
|
{
|
|
this->ShowProcessingOverlayMessage(ResponseData.message);
|
|
}
|
|
else
|
|
{
|
|
if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "op_sys")
|
|
{
|
|
for (const FJSONFieldValueData &FieldValue : ResponseData.fields[0].values)
|
|
{
|
|
this->OSList.Add(FieldValue.name);
|
|
}
|
|
}
|
|
|
|
this->CheckIfAllInitialResponsesAreIn();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->ServerConnectionError(Request->GetStatus());
|
|
}
|
|
}
|
|
|
|
void UBugSubmissionForm::CheckIfAllInitialResponsesAreIn()
|
|
{
|
|
if (!this->ComponentList.IsEmpty() && !this->VersionsList.IsEmpty() && !this->SeverityList.IsEmpty() &&
|
|
!this->PlatformsList.IsEmpty() && !this->OSList.IsEmpty())
|
|
{
|
|
// Set these as defaults in case nothing below changes this setting
|
|
this->HardwareButton->SetText(FText::AsCultureInvariant("All"));
|
|
this->OSButton->SetText(FText::AsCultureInvariant("All"));
|
|
|
|
if (this->PlatformsList.Contains("PC")) // Try our best to auto-detect PC hardware
|
|
{
|
|
if (UGameplayStatics::GetPlatformName() == "Windows" && this->OSList.Contains("Windows"))
|
|
{
|
|
this->HardwareButton->SetText(FText::AsCultureInvariant("PC"));
|
|
this->OSButton->SetText(FText::AsCultureInvariant("Windows"));
|
|
}
|
|
else if (UGameplayStatics::GetPlatformName() == "Linux" && this->OSList.Contains("Linux"))
|
|
{
|
|
this->HardwareButton->SetText(FText::AsCultureInvariant("PC"));
|
|
this->OSButton->SetText(FText::AsCultureInvariant("Linux"));
|
|
}
|
|
else if (UGameplayStatics::GetPlatformName() == "Mac" && this->OSList.Contains("Mac OS"))
|
|
{
|
|
this->HardwareButton->SetText(FText::AsCultureInvariant("All"));
|
|
this->OSButton->SetText(FText::AsCultureInvariant("Mac OS"));
|
|
}
|
|
}
|
|
if (UGameplayStatics::GetPlatformName() == "Mac") // Try our best to auto-detect Macintosh hardware
|
|
{
|
|
if (this->PlatformsList.Contains("Macintosh"))
|
|
{
|
|
if (this->OSList.Contains("Mac OS"))
|
|
{
|
|
this->HardwareButton->SetText(FText::AsCultureInvariant("Macintosh"));
|
|
this->OSButton->SetText(FText::AsCultureInvariant("Mac OS"));
|
|
}
|
|
}
|
|
}
|
|
|
|
this->HideProcessingOverlay();
|
|
}
|
|
}
|
|
|
|
void UBugSubmissionForm::ServerConnectionError(const EHttpRequestStatus::Type Status)
|
|
{
|
|
switch (Status) {
|
|
case EHttpRequestStatus::Failed_ConnectionError:
|
|
this->ShowProcessingOverlayMessage("Unable to connect to the server");
|
|
break;
|
|
case EHttpRequestStatus::NotStarted:
|
|
this->ShowProcessingOverlayMessage("Connection could not start");
|
|
break;
|
|
case EHttpRequestStatus::Failed:
|
|
this->ShowProcessingOverlayMessage("Connection failed");
|
|
break;
|
|
default:
|
|
this->ShowProcessingOverlayMessage("Request failed for unknown reasons");
|
|
}
|
|
}
|