Jira now works too. Too many changes to document still.

This commit is contained in:
Jamie Greunbaum 2023-04-09 23:01:09 -04:00
parent a11bd66fbc
commit 504655d19a
15 changed files with 566 additions and 413 deletions

View File

@ -4,7 +4,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
//#include "BugzillaJSONStructs.generated.h" #include "BugzillaJSONStructs.generated.h"
/** /**

View File

@ -4,4 +4,4 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
//#include "JiraJSONStructs.generated.h" #include "JiraJSONStructs.generated.h"

View File

@ -21,13 +21,17 @@ void UBugMarkerSubsystem::Initialize(FSubsystemCollectionBase &Collection)
this->ServerAPI = NewObject<UServerBugzillaAPI>(this); this->ServerAPI = NewObject<UServerBugzillaAPI>(this);
break; break;
case EBugReportPlatform::Jira: case EBugReportPlatform::Jira:
// this->ServerAPI = NewObject<UServerJiraAPI>(this); this->ServerAPI = NewObject<UServerJiraAPI>(this);
// break; break;
default: default:
this->ServerAPI = NewObject<UServerAPI>(this); this->ServerAPI = NewObject<UServerAPI>(this);
} }
if (!this->ServerAPI) if (this->ServerAPI)
{
this->ServerAPI->Initialize();
}
else
{ {
UE_LOG(BugMarkerSubsystemLog, Error, TEXT("Could not create Server API class; this will certainly crash eventually")); UE_LOG(BugMarkerSubsystemLog, Error, TEXT("Could not create Server API class; this will certainly crash eventually"));
} }
@ -79,6 +83,7 @@ void UBugMarkerSubsystem::HideBugMarkers()
} }
} }
void UBugMarkerSubsystem::PrepareSubmissionFormData() void UBugMarkerSubsystem::PrepareSubmissionFormData()
{ {
this->ServerAPI->FormDataResponse.BindUObject(this, &UBugMarkerSubsystem::FormPrepResponseCallback); this->ServerAPI->FormDataResponse.BindUObject(this, &UBugMarkerSubsystem::FormPrepResponseCallback);
@ -106,7 +111,7 @@ void UBugMarkerSubsystem::LoadNewBatch()
const FUnrealzillaBugData BugData = this->BugBatch[0]; const FUnrealzillaBugData BugData = this->BugBatch[0];
FString LocationString, UpVectorString; FString LocationString, UpVectorString;
BugData.MapLocation.Split(":", &LocationString, &UpVectorString); BugData.MarkerLocation.Split(":", &LocationString, &UpVectorString);
FString LocationX, LocationY, LocationZ; FString LocationX, LocationY, LocationZ;
LocationString.Split(",", &LocationX, &LocationY); LocationString.Split(",", &LocationX, &LocationY);

View File

@ -36,7 +36,7 @@ void UBugSubmissionForm::NativeOnInitialized()
{ {
this->ShowProcessingOverlayLoading(); this->ShowProcessingOverlayLoading();
UBugMarkerSubsystem *BugMarkerSubsystem = UGameplayStatics::GetPlayerController(this, 0)->GetLocalPlayer()->GetSubsystem<UBugMarkerSubsystem>(); UBugMarkerSubsystem *BugMarkerSubsystem = UBugMarkerSubsystem::GetBugMarkerSubsystem(this);
BugMarkerSubsystem->FormPrepResponse.BindUObject(this, &UBugSubmissionForm::PrepareFormData); BugMarkerSubsystem->FormPrepResponse.BindUObject(this, &UBugSubmissionForm::PrepareFormData);
BugMarkerSubsystem->PrepareSubmissionFormData(); BugMarkerSubsystem->PrepareSubmissionFormData();
@ -95,13 +95,13 @@ void UBugSubmissionForm::ShowProcessingOverlayLoading()
this->ProcessingRequestErrorBox->SetVisibility(ESlateVisibility::Collapsed); this->ProcessingRequestErrorBox->SetVisibility(ESlateVisibility::Collapsed);
} }
void UBugSubmissionForm::ShowProcessingOverlayMessage(const FString Message, const bool bExitAfterConfirm) void UBugSubmissionForm::ShowProcessingOverlayMessage(const FUnrealzillaErrorData &Error)
{ {
this->ProcessingRequestErrorText->SetText(FText::AsCultureInvariant(Message)); this->ProcessingRequestErrorText->SetText(FText::AsCultureInvariant(Error.ErrorMessage));
this->ProcessingRequestOverlay->SetVisibility(ESlateVisibility::Visible); this->ProcessingRequestOverlay->SetVisibility(ESlateVisibility::Visible);
this->ProcessingRequestThrobber->SetVisibility(ESlateVisibility::Collapsed); this->ProcessingRequestThrobber->SetVisibility(ESlateVisibility::Collapsed);
this->ProcessingRequestErrorBox->SetVisibility(ESlateVisibility::Visible); this->ProcessingRequestErrorBox->SetVisibility(ESlateVisibility::Visible);
if (bExitAfterConfirm) if (Error.bCancelForm)
{ {
this->ProcessingRequestErrorButton->OnClicked().AddUObject(this, &UBugSubmissionForm::CancelForm); this->ProcessingRequestErrorButton->OnClicked().AddUObject(this, &UBugSubmissionForm::CancelForm);
} }
@ -123,12 +123,13 @@ void UBugSubmissionForm::SubmitForm()
PostData.Component = this->ComponentButton->GetText().ToString(); PostData.Component = this->ComponentButton->GetText().ToString();
PostData.Severity = this->SeverityButton->GetText().ToString(); PostData.Severity = this->SeverityButton->GetText().ToString();
PostData.MapName = this->MapName.ToString(); PostData.MapName = this->MapName.ToString();
PostData.MapLocation = this->MarkerLocation.ToString(); PostData.MarkerLocation = this->MarkerLocation.ToString();
PostData.Summary = this->SummaryEntryBox->GetText().ToString(); PostData.Summary = this->SummaryEntryBox->GetText().ToString();
PostData.Comment = this->CommentEntryBox->GetText().ToString(); PostData.Comment = this->CommentEntryBox->GetText().ToString();
UBugMarkerSubsystem *BugMarkerSubsystem = UGameplayStatics::GetPlayerController(this, 0)->GetLocalPlayer()->GetSubsystem<UBugMarkerSubsystem>(); UBugMarkerSubsystem *BugMarkerSubsystem = UBugMarkerSubsystem::GetBugMarkerSubsystem(this);
BugMarkerSubsystem->FormPostResponse.BindUObject(this, &UBugSubmissionForm::UpdateReportMarker); BugMarkerSubsystem->FormPostResponse.BindUObject(this, &UBugSubmissionForm::UpdateReportMarker);
BugMarkerSubsystem->ErrorResponse.BindUObject(this, &UBugSubmissionForm::ShowProcessingOverlayMessage);
BugMarkerSubsystem->SubmitForm(PostData); BugMarkerSubsystem->SubmitForm(PostData);
this->OnFormSubmit.ExecuteIfBound(); this->OnFormSubmit.ExecuteIfBound();
@ -136,7 +137,16 @@ void UBugSubmissionForm::SubmitForm()
void UBugSubmissionForm::UpdateReportMarker(const FUnrealzillaBugData &BugData) void UBugSubmissionForm::UpdateReportMarker(const FUnrealzillaBugData &BugData)
{ {
this->BugMarkerActor->SetBugData(BugData); UBugMarkerSubsystem *BugMarkerSubsystem = UBugMarkerSubsystem::GetBugMarkerSubsystem(this);
if (BugMarkerSubsystem->AreMarkersVisible())
{
this->BugMarkerActor->SetBugData(BugData);
BugMarkerSubsystem->AddBugMarker(this->BugMarkerActor);
}
else
{
this->BugMarkerActor->Destroy();
}
this->CloseForm(); this->CloseForm();
} }
@ -159,5 +169,5 @@ void UBugSubmissionForm::CloseForm()
void UBugSubmissionForm::ErrorResponseCallback(const FUnrealzillaErrorData &Error) void UBugSubmissionForm::ErrorResponseCallback(const FUnrealzillaErrorData &Error)
{ {
this->ShowProcessingOverlayMessage(Error.ErrorMessage); this->ShowProcessingOverlayMessage(Error);
} }

View File

@ -30,20 +30,17 @@ void UServerAPI::ServerConnectionError(const EHttpRequestStatus::Type Status)
{ {
switch (Status) { switch (Status) {
case EHttpRequestStatus::Failed_ConnectionError: case EHttpRequestStatus::Failed_ConnectionError:
this->CreateError("Unable to connect to the server"); this->CreateError("There was an error connecting to the server. Please retry.", true);
//this->ShowProcessingOverlayMessage("Unable to connect to the server", true);
break;
case EHttpRequestStatus::NotStarted:
this->CreateError("Connection could not start");
//this->ShowProcessingOverlayMessage("Connection could not start", true);
break; break;
case EHttpRequestStatus::Failed: case EHttpRequestStatus::Failed:
this->CreateError("Connection failed"); this->CreateError("Connection to the server completed but then failed.", true);
//this->ShowProcessingOverlayMessage("Connection failed", true); break;
case EHttpRequestStatus::NotStarted:
this->CreateError("Connection was not started.");
break;
case EHttpRequestStatus::Processing:
this->CreateError("Connection is still being processed.");
break; break;
default:
this->CreateError("Request failed for unknown reasons");
//this->ShowProcessingOverlayMessage("Request failed for unknown reasons", true);
} }
} }
@ -83,11 +80,12 @@ void UServerAPI::CreateError(const EErrorVerb &Verb, const FBugzillaJSONBugRespo
Error.DocumentationLink = Data.documentation; Error.DocumentationLink = Data.documentation;
this->ErrorResponse.Execute(Error); this->ErrorResponse.Execute(Error);
} }
void UServerAPI::CreateError(const FString &ErrorMessage) void UServerAPI::CreateError(const FString &ErrorMessage, const bool bCancelForm)
{ {
FUnrealzillaErrorData Error; FUnrealzillaErrorData Error;
Error.ErrorVerb = EErrorVerb::None; Error.ErrorVerb = EErrorVerb::None;
Error.ErrorMessage = ErrorMessage; Error.ErrorMessage = ErrorMessage;
Error.bCancelForm = bCancelForm;
this->ErrorResponse.Execute(Error); this->ErrorResponse.Execute(Error);
} }

View File

@ -10,7 +10,7 @@
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
UServerBugzillaAPI::UServerBugzillaAPI() void UServerBugzillaAPI::Initialize()
{ {
this->FullURL = UServerAPI::URLBuilder( this->FullURL = UServerAPI::URLBuilder(
GetDefault<UUnrealzillaGlobalSettings>()->BugzillaSubmissionServer, GetDefault<UUnrealzillaGlobalSettings>()->BugzillaSubmissionServer,
@ -74,7 +74,7 @@ void UServerBugzillaAPI::ListOfBugsResponse(FHttpRequestPtr Request, FHttpRespon
Bug.Summary = BugzillaData.summary; Bug.Summary = BugzillaData.summary;
Bug.Component = BugzillaData.component; Bug.Component = BugzillaData.component;
Bug.MapName = BugzillaData.cf_mapname; Bug.MapName = BugzillaData.cf_mapname;
Bug.MapLocation = BugzillaData.cf_location; Bug.MarkerLocation = BugzillaData.cf_location;
Bug.Platform = BugzillaData.platform; Bug.Platform = BugzillaData.platform;
Bug.OperatingSystem = BugzillaData.op_sys; Bug.OperatingSystem = BugzillaData.op_sys;
Bug.Severity = BugzillaData.severity; Bug.Severity = BugzillaData.severity;
@ -140,7 +140,7 @@ void UServerBugzillaAPI::SendFormData(const FUnrealzillaPostData &PostData)
TMap<FString, FString> QueryData; TMap<FString, FString> QueryData;
QueryData.Add("api_key", GetDefault<UUnrealzillaGlobalSettings>()->BugzillaAPIKey); QueryData.Add("api_key", GetDefault<UUnrealzillaGlobalSettings>()->BugzillaAPIKey);
const FString DefaultStatus = GetDefault<UUnrealzillaGlobalSettings>()->DefaultStatus; const FString DefaultStatus = GetDefault<UUnrealzillaGlobalSettings>()->BugzillaDefaultStatus;
FBugzillaJSONPostBug PostDataJSON; FBugzillaJSONPostBug PostDataJSON;
PostDataJSON.product = GetDefault<UUnrealzillaGlobalSettings>()->BugzillaProductName; PostDataJSON.product = GetDefault<UUnrealzillaGlobalSettings>()->BugzillaProductName;
@ -150,7 +150,7 @@ void UServerBugzillaAPI::SendFormData(const FUnrealzillaPostData &PostData)
PostDataJSON.component = PostData.Component; PostDataJSON.component = PostData.Component;
PostDataJSON.severity = PostData.Severity; PostDataJSON.severity = PostData.Severity;
PostDataJSON.cf_mapname = PostData.MapName; PostDataJSON.cf_mapname = PostData.MapName;
PostDataJSON.cf_location = PostData.MapLocation; PostDataJSON.cf_location = PostData.MarkerLocation;
PostDataJSON.summary = PostData.Summary; PostDataJSON.summary = PostData.Summary;
PostDataJSON.description = PostData.Comment; PostDataJSON.description = PostData.Comment;
if (!DefaultStatus.IsEmpty()) if (!DefaultStatus.IsEmpty())
@ -278,7 +278,7 @@ void UServerBugzillaAPI::ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request,
Bug.Summary = ResponseData.bugs[0].summary; Bug.Summary = ResponseData.bugs[0].summary;
Bug.Component = ResponseData.bugs[0].component; Bug.Component = ResponseData.bugs[0].component;
Bug.MapName = ResponseData.bugs[0].cf_mapname; Bug.MapName = ResponseData.bugs[0].cf_mapname;
Bug.MapLocation = ResponseData.bugs[0].cf_location; Bug.MarkerLocation = ResponseData.bugs[0].cf_location;
Bug.Platform = ResponseData.bugs[0].platform; Bug.Platform = ResponseData.bugs[0].platform;
Bug.OperatingSystem = ResponseData.bugs[0].op_sys; Bug.OperatingSystem = ResponseData.bugs[0].op_sys;
Bug.Severity = ResponseData.bugs[0].severity; Bug.Severity = ResponseData.bugs[0].severity;

View File

@ -10,437 +10,332 @@
#include "Kismet/GameplayStatics.h" #include "Kismet/GameplayStatics.h"
UServerJiraAPI::UServerJiraAPI() void UServerJiraAPI::Initialize()
{ {
this->FullURL = UServerAPI::URLBuilder( this->FullURL = UServerAPI::URLBuilder(
GetDefault<UUnrealzillaGlobalSettings>()->JiraSubmissionServer, GetDefault<UUnrealzillaGlobalSettings>()->JiraSubmissionServer,
GetDefault<UUnrealzillaGlobalSettings>()->JiraRESTURI GetDefault<UUnrealzillaGlobalSettings>()->JiraRESTURI
); );
FHttpModule &HttpModule = FHttpModule::Get();
// Get board data
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> BoardRequest = HttpModule.CreateRequest();
BoardRequest->SetVerb(TEXT("GET"));
BoardRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
BoardRequest->AppendToHeader(TEXT("Authorization"), TEXT("Basic ") + this->GenerateAuthString());
BoardRequest->SetURL(this->FullURL + "agile/1.0/board");
BoardRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ListOfBoardsResponse);
BoardRequest->ProcessRequest();
// Get custom field data
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> FieldsRequest = HttpModule.CreateRequest();
FieldsRequest->SetVerb(TEXT("GET"));
FieldsRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
FieldsRequest->AppendToHeader(TEXT("Authorization"), TEXT("Basic ") + this->GenerateAuthString());
FieldsRequest->SetURL(this->FullURL + "api/2/field");
FieldsRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ListOfFieldsResponse);
FieldsRequest->ProcessRequest();
}
void UServerJiraAPI::ListOfBoardsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
const FString &JSONResponse = Response->GetContentAsString();
TSharedPtr<FJsonObject> JSON = MakeShareable(new FJsonObject);
const TSharedRef<TJsonReader<>> &Reader = TJsonReaderFactory<>::Create(JSONResponse);
if (FJsonSerializer::Deserialize(Reader, JSON))
{
const TArray<TSharedPtr<FJsonValue>> &BoardsArray = JSON->GetArrayField("values");
for (const TSharedPtr<FJsonValue> &BoardValue : BoardsArray)
{
const TSharedPtr<FJsonObject> &Board = BoardValue->AsObject();
const TSharedPtr<FJsonObject> &Location = Board->GetObjectField("location");
if (Location->GetStringField("projectName") == GetDefault<UUnrealzillaGlobalSettings>()->JiraProjectName)
{
this->BoardID = Board->GetIntegerField("id");
this->ProjectID = Location->GetIntegerField("projectId");
this->ProjectKey = Location->GetStringField("projectKey");
this->ProjectAvatarURI = Location->GetStringField("avatarURI");
}
}
}
else
{
this->CreateError("Could not retrieve board data.");
}
}
else
{
this->CreateError("Could not retrieve board data.");
}
}
void UServerJiraAPI::ListOfFieldsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
const FString &JSONResponse = "{\"fields\":" + Response->GetContentAsString() + "}";
TSharedPtr<FJsonObject> JSON = MakeShareable(new FJsonObject);
const TSharedRef<TJsonReader<>> &Reader = TJsonReaderFactory<>::Create(JSONResponse);
if (FJsonSerializer::Deserialize(Reader, JSON))
{
const TArray<TSharedPtr<FJsonValue>> &FieldArray = JSON->GetArrayField("fields");
for (const TSharedPtr<FJsonValue> &FieldValue : FieldArray)
{
const TSharedPtr<FJsonObject> &Field = FieldValue->AsObject();
const FString &Name = Field->GetStringField("name");
const FString &Key = Field->GetStringField("key");
if (Name == GetDefault<UUnrealzillaGlobalSettings>()->JiraMapNameField)
{
this->MapNameCustomField = Key;
}
else if (Name == GetDefault<UUnrealzillaGlobalSettings>()->JiraMarkerLocationField)
{
this->MarkerLocationCustomField = Key;
}
else if (Name == GetDefault<UUnrealzillaGlobalSettings>()->JiraDepartmentField)
{
this->DepartmentCustomField = Key;
}
else if (Name == GetDefault<UUnrealzillaGlobalSettings>()->JiraSeverityField)
{
this->SeverityCustomField = Key;
}
else if (Name == GetDefault<UUnrealzillaGlobalSettings>()->JiraPlatformField)
{
this->PlatformCustomField = Key;
}
else if (Name == GetDefault<UUnrealzillaGlobalSettings>()->JiraVersionField)
{
this->VersionCustomField = Key;
}
if (!this->MapNameCustomField.IsEmpty() &&
!this->MarkerLocationCustomField.IsEmpty() &&
!this->DepartmentCustomField.IsEmpty() &&
!this->SeverityCustomField.IsEmpty() &&
!this->PlatformCustomField.IsEmpty() &&
!this->VersionCustomField.IsEmpty())
{
break;
}
}
}
else
{
this->CreateError("Could not deserialise JSON.");
}
}
} }
void UServerJiraAPI::ReturnListOfBugs() void UServerJiraAPI::ReturnListOfBugs()
{ {
const FString FullURL = GetDefault<UUnrealzillaGlobalSettings>()->JiraSubmissionServer + "/rest";
TArray<FString> StatusQueries;
if (GetDefault<UUnrealzillaGlobalSettings>()->bShowUnresolvedBugs)
{
for (const FString Unresolved : GetDefault<UUnrealzillaGlobalSettings>()->UnresolvedStatuses)
{
StatusQueries.Add("status=" + Unresolved);
}
}
if (GetDefault<UUnrealzillaGlobalSettings>()->bShowInProgressBugs)
{
for (const FString InProgress : GetDefault<UUnrealzillaGlobalSettings>()->InProgressStatuses)
{
StatusQueries.Add("status=" + InProgress);
}
}
if (GetDefault<UUnrealzillaGlobalSettings>()->bShowResolvedBugs)
{
for (const FString Resolved : GetDefault<UUnrealzillaGlobalSettings>()->ResolvedStatuses)
{
StatusQueries.Add("status=" + Resolved);
}
}
StatusQueries.Add("cf_mapname=" + this->GetWorld()->GetMapName().RightChop(this->GetWorld()->StreamingLevelsPrefix.Len()));
StatusQueries.Add("api_key=" + GetDefault<UUnrealzillaGlobalSettings>()->JiraAPIKey);
const FString QueryString = FString::Join(StatusQueries, TEXT("&"));
FHttpModule &HttpModule = FHttpModule::Get(); FHttpModule &HttpModule = FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> SeverityRequest = HttpModule.CreateRequest(); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> SeverityRequest = HttpModule.CreateRequest();
SeverityRequest->SetVerb(TEXT("GET")); SeverityRequest->SetVerb(TEXT("GET"));
SeverityRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); SeverityRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
SeverityRequest->SetURL(FullURL + "/bug" + "?" + QueryString); SeverityRequest->AppendToHeader(TEXT("Authorization"), TEXT("Basic ") + this->GenerateAuthString());
SeverityRequest->SetURL(this->FullURL + "agile/1.0/board/" + FString::FromInt(this->BoardID) + "/issue?expand=names&fields=summary,description,issuetype,status,priority" + (this->MapNameCustomField.IsEmpty()?"":(","+this->MapNameCustomField)) + (this->MarkerLocationCustomField.IsEmpty()?"":(","+this->MarkerLocationCustomField)));
SeverityRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ListOfBugsResponse); SeverityRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ListOfBugsResponse);
SeverityRequest->ProcessRequest(); SeverityRequest->ProcessRequest();
} }
void UServerJiraAPI::ListOfBugsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) void UServerJiraAPI::ListOfBugsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{ {
if (Success) if (Success)
{ {
FBugzillaJSONBugResponse ResponseData; TArray<FUnrealzillaBugData> BugData;
FString JSONResponse = Response->GetContentAsString();
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
if (!ResponseData.error) const FString JSONResponse = Response->GetContentAsString();
TSharedPtr<FJsonObject> JSON = MakeShareable(new FJsonObject);
const TSharedRef<TJsonReader<>> &Reader = TJsonReaderFactory<>::Create(JSONResponse);
if (FJsonSerializer::Deserialize(Reader, JSON))
{ {
TArray<FUnrealzillaBugData> BugData; const TArray<TSharedPtr<FJsonValue>> &IssuesArray = JSON->GetArrayField("issues");
for (const FBugzillaJSONBugData &BugzillaData : ResponseData.bugs) for (const TSharedPtr<FJsonValue> &IssueValue : IssuesArray)
{ {
FUnrealzillaBugData Bug; const TSharedPtr<FJsonObject> &Issue = IssueValue->AsObject();
Bug.ID = BugzillaData.id; const TSharedPtr<FJsonObject> &Fields = Issue->GetObjectField("fields");
Bug.Summary = BugzillaData.summary; const TSharedPtr<FJsonObject> &Status = Fields->GetObjectField("status");
Bug.Component = BugzillaData.component; const FString StatusName = Status->GetStringField("name");
Bug.MapName = BugzillaData.cf_mapname;
Bug.MapLocation = BugzillaData.cf_location; if ((GetDefault<UUnrealzillaGlobalSettings>()->bShowUnresolvedBugs && GetDefault<UUnrealzillaGlobalSettings>()->UnresolvedStatuses.Contains(StatusName)) ||
Bug.Platform = BugzillaData.platform; (GetDefault<UUnrealzillaGlobalSettings>()->bShowInProgressBugs && GetDefault<UUnrealzillaGlobalSettings>()->InProgressStatuses.Contains(StatusName)) ||
Bug.OperatingSystem = BugzillaData.op_sys; (GetDefault<UUnrealzillaGlobalSettings>()->bShowResolvedBugs && GetDefault<UUnrealzillaGlobalSettings>()->ResolvedStatuses.Contains(StatusName)))
Bug.Severity = BugzillaData.severity; {
Bug.Status = BugzillaData.status; const TSharedPtr<FJsonObject> &Priority = Fields->GetObjectField("priority");
Bug.Resolution = BugzillaData.resolution;
Bug.DuplicateOf = BugzillaData.dupe_of; FUnrealzillaBugData Bug;
Bug.bIsOpen = BugzillaData.is_open; Bug.ID = Issue->GetNumberField("id");
BugData.Add(Bug); Bug.Status = StatusName;
Bug.Summary = Fields->GetStringField("summary");
Bug.Severity = Priority->GetStringField("name");
//Bug.Component = BugzillaData.component;
//Bug.Platform = BugzillaData.platform;
//Bug.OperatingSystem = BugzillaData.op_sys;
//Bug.Resolution = BugzillaData.resolution;
//Bug.DuplicateOf = BugzillaData.dupe_of;
//Bug.bIsOpen = BugzillaData.is_open;
if (!this->MapNameCustomField.IsEmpty())
{
Bug.MapName = Fields->GetStringField(this->MapNameCustomField);
}
if (!this->MarkerLocationCustomField.IsEmpty())
{
Bug.MarkerLocation = Fields->GetStringField(this->MarkerLocationCustomField);
}
BugData.Add(Bug);
}
} }
this->BugDataResponse.Execute(BugData);
} }
else else
{ {
this->CreateError(EErrorVerb::GET, ResponseData); this->CreateError("Could not deserialise JSON.");
} }
this->BugDataResponse.Execute(BugData);
}
else
{
this->ServerConnectionError(Request->GetStatus());
} }
} }
void UServerJiraAPI::PrepareForm() void UServerJiraAPI::PrepareForm()
{ {
const FString FullURL = GetDefault<UUnrealzillaGlobalSettings>()->JiraSubmissionServer + "/rest";
// Assemble query data into key:value pairs // Assemble query data into key:value pairs
TMap<FString, FString> QueryData; TMap<FString, FString> QueryData;
QueryData.Add("api_key", GetDefault<UUnrealzillaGlobalSettings>()->JiraAPIKey); QueryData.Add("issuetypeNames", GetDefault<UUnrealzillaGlobalSettings>()->JiraBugIssueType);
QueryData.Add("expand", "projects.issuetypes.fields");
const FString QueryString = UServerAPI::FormatQueryString(QueryData); const FString QueryString = UServerAPI::FormatQueryString(QueryData);
// Query the server for information about the current product // Keep track of how many of these tasks have completed so we can check their completion status later.
FHttpModule &HttpModule = FHttpModule::Get(); this->FieldListsCompletion = 0;
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> ProductRequest = HttpModule.CreateRequest(); this->FieldListsCompletionMax = 2;
ProductRequest->SetVerb(TEXT("GET"));
ProductRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
ProductRequest->SetURL(FullURL + "/product/" + GetDefault<UUnrealzillaGlobalSettings>()->JiraProjectName + "?" + QueryString);
ProductRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::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, &UServerJiraAPI::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, &UServerJiraAPI::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, &UServerJiraAPI::ServerOSInfoResponse);
OSRequest->ProcessRequest();
}
void UServerJiraAPI::SendFormData(const FUnrealzillaPostData &PostData)
{
const FString FullURL = GetDefault<UUnrealzillaGlobalSettings>()->JiraSubmissionServer + "/rest";
const FString RequestURL = "/bug";
// Assemble query data into key:value pairs
TMap<FString, FString> QueryData;
QueryData.Add("api_key", GetDefault<UUnrealzillaGlobalSettings>()->JiraAPIKey);
const FString DefaultStatus = GetDefault<UUnrealzillaGlobalSettings>()->DefaultStatus;
FBugzillaJSONPostBug PostDataJSON;
PostDataJSON.product = GetDefault<UUnrealzillaGlobalSettings>()->JiraProjectName;
PostDataJSON.version = PostData.Version;
PostDataJSON.platform = PostData.Platform;
PostDataJSON.op_sys = PostData.OS;
PostDataJSON.component = PostData.Component;
PostDataJSON.severity = PostData.Severity;
PostDataJSON.cf_mapname = PostData.MapName;
PostDataJSON.cf_location = PostData.MapLocation;
PostDataJSON.summary = PostData.Summary;
PostDataJSON.description = PostData.Comment;
if (!DefaultStatus.IsEmpty())
{
PostDataJSON.status = DefaultStatus;
}
if (PostDataJSON.version.IsEmpty())
{
this->CreateError("You must select a version number.");
return;
}
if (PostDataJSON.platform.IsEmpty() || PostDataJSON.op_sys.IsEmpty())
{
PostDataJSON.platform = "All";
PostDataJSON.op_sys = "All";
}
if (PostDataJSON.component.IsEmpty())
{
this->CreateError("You must select a component.");
return;
}
if (PostDataJSON.severity.IsEmpty())
{
this->CreateError("You must select a severity level.");
return;
}
if (PostDataJSON.summary.IsEmpty())
{
this->CreateError("You must provide a summary.");
return;
}
FString PostJsonString;
FJsonObjectConverter::UStructToJsonObjectString(PostDataJSON, PostJsonString);
FHttpModule &HttpModule = FHttpModule::Get(); FHttpModule &HttpModule = FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = HttpModule.CreateRequest();
Request->SetVerb(TEXT("POST")); // Send a query to retrieve field options
Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); TSharedRef<IHttpRequest, ESPMode::ThreadSafe> FieldsRequest = HttpModule.CreateRequest();
Request->SetURL(FullURL + RequestURL + "?" + UServerAPI::FormatQueryString(QueryData)); FieldsRequest->SetVerb(TEXT("GET"));
Request->SetContentAsString(PostJsonString); FieldsRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
Request->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerPOSTResponse); FieldsRequest->AppendToHeader(TEXT("Authorization"), TEXT("Basic ") + this->GenerateAuthString());
Request->ProcessRequest(); FieldsRequest->SetURL(this->FullURL + "api/2/issue/createmeta?" + QueryString);
FieldsRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerFieldOptionsResponse);
FieldsRequest->ProcessRequest();
// Send a second query to retrieve version numbers
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> VersionsRequest = HttpModule.CreateRequest();
VersionsRequest->SetVerb(TEXT("GET"));
VersionsRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
VersionsRequest->AppendToHeader(TEXT("Authorization"), TEXT("Basic ") + this->GenerateAuthString());
VersionsRequest->SetURL(this->FullURL + "api/2/project/" + FString::FromInt(this->ProjectID) + "/version");
VersionsRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerVersionsResponse);
VersionsRequest->ProcessRequest();
} }
void UServerJiraAPI::ServerFieldOptionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
void UServerJiraAPI::ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{ {
if (Success) if (Success)
{ {
FBugzillaJSONPostResponse ResponseData; const FString &JSONResponse = Response->GetContentAsString();
FString JSONResponse = Response->GetContentAsString();
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
if (ResponseData.error) TSharedPtr<FJsonObject> JSON = MakeShareable(new FJsonObject);
const TSharedRef<TJsonReader<>> &Reader = TJsonReaderFactory<>::Create(JSONResponse);
if (FJsonSerializer::Deserialize(Reader, JSON))
{ {
this->CreateError(EErrorVerb::POST, ResponseData); const TArray<TSharedPtr<FJsonValue>> &ProjectsArray = JSON->GetArrayField("projects");
} for (const TSharedPtr<FJsonValue> &ProjectValue : ProjectsArray)
else
{
// Use the response's bug ID to get the info from the newly filed bug report and update its marker
const FString FullURL = GetDefault<UUnrealzillaGlobalSettings>()->JiraSubmissionServer + "/rest";
TArray<FString> StatusQueries;
StatusQueries.Add("id=" + FString::FromInt(ResponseData.id));
if (GetDefault<UUnrealzillaGlobalSettings>()->bShowUnresolvedBugs)
{ {
for (const FString Unresolved : GetDefault<UUnrealzillaGlobalSettings>()->UnresolvedStatuses) const TSharedPtr<FJsonObject> &Project = ProjectValue->AsObject();
if (Project->GetStringField("name") == GetDefault<UUnrealzillaGlobalSettings>()->JiraProjectName)
{ {
StatusQueries.Add("status=" + Unresolved); const TArray<TSharedPtr<FJsonValue>> &IssueTypes = Project->GetArrayField("issuetypes");
} for (const TSharedPtr<FJsonValue> &IssueTypeValue : IssueTypes)
}
if (GetDefault<UUnrealzillaGlobalSettings>()->bShowInProgressBugs)
{
for (const FString InProgress : GetDefault<UUnrealzillaGlobalSettings>()->InProgressStatuses)
{
StatusQueries.Add("status=" + InProgress);
}
}
if (GetDefault<UUnrealzillaGlobalSettings>()->bShowResolvedBugs)
{
for (const FString Resolved : GetDefault<UUnrealzillaGlobalSettings>()->ResolvedStatuses)
{
StatusQueries.Add("status=" + Resolved);
}
}
StatusQueries.Add("cf_mapname=" + this->GetWorld()->GetMapName().RightChop(this->GetWorld()->StreamingLevelsPrefix.Len()));
StatusQueries.Add("api_key=" + GetDefault<UUnrealzillaGlobalSettings>()->JiraAPIKey);
const FString QueryString = FString::Join(StatusQueries, TEXT("&"));
FHttpModule &HttpModule = FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> SeverityRequest = HttpModule.CreateRequest();
SeverityRequest->SetVerb(TEXT("GET"));
SeverityRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
SeverityRequest->SetURL(FullURL + "/bug" + "?" + QueryString);
SeverityRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerPOSTUpdateMarkerResponse);
SeverityRequest->ProcessRequest();
}
}
else
{
this->ServerConnectionError(Request->GetStatus());
}
}
void UServerJiraAPI::ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
FBugzillaJSONBugResponse ResponseData;
FString JSONResponse = Response->GetContentAsString();
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
if (ResponseData.error)
{
this->CreateError(EErrorVerb::GET, ResponseData);
}
else
{
if (!ResponseData.bugs.IsEmpty())
{
TArray<FUnrealzillaBugData> BugData;
FUnrealzillaBugData Bug;
Bug.ID = ResponseData.bugs[0].id;
Bug.Summary = ResponseData.bugs[0].summary;
Bug.Component = ResponseData.bugs[0].component;
Bug.MapName = ResponseData.bugs[0].cf_mapname;
Bug.MapLocation = ResponseData.bugs[0].cf_location;
Bug.Platform = ResponseData.bugs[0].platform;
Bug.OperatingSystem = ResponseData.bugs[0].op_sys;
Bug.Severity = ResponseData.bugs[0].severity;
Bug.Status = ResponseData.bugs[0].status;
Bug.Resolution = ResponseData.bugs[0].resolution;
Bug.DuplicateOf = ResponseData.bugs[0].dupe_of;
Bug.bIsOpen = ResponseData.bugs[0].is_open;
BugData.Add(Bug);
this->BugDataResponse.Execute(BugData);
}
}
}
else
{
this->ServerConnectionError(Request->GetStatus());
}
}
void UServerJiraAPI::ServerProductInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
FBugzillaJSONProductResponse ResponseData;
FString JSONResponse = Response->GetContentAsString();
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
if (ResponseData.error)
{
//this->ShowProcessingOverlayMessage(ResponseData.message);
this->CreateError(EErrorVerb::GET, ResponseData);
}
else
{
if (!ResponseData.products.IsEmpty())
{
const FBugzillaJSONProductData &ProductData = ResponseData.products[0];
if (ProductData.name == GetDefault<UUnrealzillaGlobalSettings>()->JiraProjectName)
{
for (const FBugzillaJSONComponentData &ComponentData : ProductData.components)
{ {
this->ComponentsList.Add(ComponentData.name); const TSharedPtr<FJsonObject> &IssueType = IssueTypeValue->AsObject();
const TSharedPtr<FJsonObject> &Fields = IssueType->GetObjectField("fields");
// After all that boilerplate code, finally get all available field options here
{
// Components
const TSharedPtr<FJsonObject> &Department = Fields->GetObjectField(this->DepartmentCustomField);
const TArray<TSharedPtr<FJsonValue>> &AllowedDepartmentsArray = Department->GetArrayField("allowedValues");
for (const TSharedPtr<FJsonValue> &AllowedDepartmentsValue : AllowedDepartmentsArray)
{
const TSharedPtr<FJsonObject> &Value = AllowedDepartmentsValue->AsObject();
this->ComponentsList.Add(Value->GetStringField("value"));
}
// Bug severity
const TSharedPtr<FJsonObject> &Severity = Fields->GetObjectField(this->SeverityCustomField);
const TArray<TSharedPtr<FJsonValue>> &AllowedSeveritiesArray = Severity->GetArrayField("allowedValues");
for (const TSharedPtr<FJsonValue> &AllowedSeveritiesValue : AllowedSeveritiesArray)
{
const TSharedPtr<FJsonObject> &Value = AllowedSeveritiesValue->AsObject();
this->SeverityList.Add(Value->GetStringField("value"));
}
// Platform
const TSharedPtr<FJsonObject> &Platform = Fields->GetObjectField(this->PlatformCustomField);
const TArray<TSharedPtr<FJsonValue>> &PlatformsArray = Platform->GetArrayField("allowedValues");
for (const TSharedPtr<FJsonValue> &PlatformsValue : PlatformsArray)
{
const TSharedPtr<FJsonObject> &Value = PlatformsValue->AsObject();
this->PlatformsList.Add(Value->GetStringField("value"));
// Also get children and add them to the OS list here
const TArray<TSharedPtr<FJsonValue>> &ChildrenArray = Value->GetArrayField("children");
for (const TSharedPtr<FJsonValue> &ChildValue : ChildrenArray)
{
const TSharedPtr<FJsonObject> &Child = ChildValue->AsObject();
this->OSList.AddUnique(Child->GetStringField("value"));
}
}
}
} }
for (const FBugzillaJSONVersionData &VersionData : ProductData.versions)
{
this->VersionsList.Add(VersionData.name);
}
}
this->CheckIfAllFormResponsesAreIn(); break;
}
else
{
FStringFormatOrderedArguments Args;
Args.Add(FStringFormatArg(GetDefault<UUnrealzillaGlobalSettings>()->JiraProjectName));
//this->ShowProcessingOverlayMessage(FString::Format(TEXT("Could not find data for a product called {0}"), Args), true);
this->CreateError(FString::Format(TEXT("Could not find data for a product called {0}"), Args));
}
}
}
else
{
this->ServerConnectionError(Request->GetStatus());
}
}
void UServerJiraAPI::ServerSeverityInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
FBugzillaJSONFieldResponse ResponseData;
FString JSONResponse = Response->GetContentAsString();
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
if (ResponseData.error)
{
//this->ShowProcessingOverlayMessage(ResponseData.message);
this->CreateError(EErrorVerb::GET, ResponseData);
}
else
{
if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "bug_severity")
{
for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values)
{
this->SeverityList.Add(FieldValue.name);
} }
} }
this->CheckIfAllFormResponsesAreIn(); this->CheckIfAllFormResponsesAreIn();
} }
else
{
this->CreateError("Could not deserialise JSON.");
}
} }
else else
{ {
this->ServerConnectionError(Request->GetStatus()); this->ServerConnectionError(Request->GetStatus());
} }
} }
void UServerJiraAPI::ServerVersionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
void UServerJiraAPI::ServerPlatformInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{ {
if (Success) if (Success)
{ {
FBugzillaJSONFieldResponse ResponseData; const FString &JSONResponse = Response->GetContentAsString();
FString JSONResponse = Response->GetContentAsString();
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
if (ResponseData.error) TSharedPtr<FJsonObject> JSON = MakeShareable(new FJsonObject);
const TSharedRef<TJsonReader<>> &Reader = TJsonReaderFactory<>::Create(JSONResponse);
if (FJsonSerializer::Deserialize(Reader, JSON))
{ {
//this->ShowProcessingOverlayMessage(ResponseData.message); const TArray<TSharedPtr<FJsonValue>> &ValuesArray = JSON->GetArrayField("values");
this->CreateError(EErrorVerb::GET, ResponseData); for (const TSharedPtr<FJsonValue> &ValueObject : ValuesArray)
}
else
{
if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "rep_platform")
{ {
for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values) const TSharedPtr<FJsonObject> &Value = ValueObject->AsObject();
{ this->VersionsList.Add(Value->GetStringField("name"));
this->PlatformsList.Add(FieldValue.name);
}
} }
this->CheckIfAllFormResponsesAreIn(); this->CheckIfAllFormResponsesAreIn();
} }
}
else
{
this->ServerConnectionError(Request->GetStatus());
}
}
void UServerJiraAPI::ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
FBugzillaJSONFieldResponse ResponseData;
FString JSONResponse = Response->GetContentAsString();
FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData);
if (ResponseData.error)
{
//this->ShowProcessingOverlayMessage(ResponseData.message);
this->CreateError(EErrorVerb::GET, ResponseData);
}
else else
{ {
if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "op_sys") this->CreateError("Could not deserialise JSON.");
{
for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values)
{
this->OSList.Add(FieldValue.name);
}
}
this->CheckIfAllFormResponsesAreIn();
} }
} }
else else
@ -448,12 +343,14 @@ void UServerJiraAPI::ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponse
this->ServerConnectionError(Request->GetStatus()); this->ServerConnectionError(Request->GetStatus());
} }
} }
void UServerJiraAPI::CheckIfAllFormResponsesAreIn() void UServerJiraAPI::CheckIfAllFormResponsesAreIn()
{ {
if (!this->ComponentsList.IsEmpty() && !this->VersionsList.IsEmpty() && !this->SeverityList.IsEmpty() && this->FieldListsCompletion++;
!this->PlatformsList.IsEmpty() && !this->OSList.IsEmpty())
if (this->FieldListsCompletion == this->FieldListsCompletionMax)
{ {
this->FieldListsCompletion = this->FieldListsCompletionMax = 0;
FUnrealzillaFormPrepData Data; FUnrealzillaFormPrepData Data;
Data.ComponentsList = this->ComponentsList; Data.ComponentsList = this->ComponentsList;
@ -468,10 +365,6 @@ void UServerJiraAPI::CheckIfAllFormResponsesAreIn()
{ {
Data.DetectedVersion = GameVersion; Data.DetectedVersion = GameVersion;
} }
else if (this->VersionsList.Contains("unspecified"))
{
Data.DetectedVersion = "unspecified";
}
else if (this->VersionsList.Contains("Latest")) else if (this->VersionsList.Contains("Latest"))
{ {
Data.DetectedVersion = "Latest"; Data.DetectedVersion = "Latest";
@ -526,3 +419,196 @@ void UServerJiraAPI::CheckIfAllFormResponsesAreIn()
this->OSList.Empty(); this->OSList.Empty();
} }
} }
void UServerJiraAPI::SendFormData(const FUnrealzillaPostData &PostData)
{
if (PostData.Summary.IsEmpty())
{
this->CreateError("You must provide a summary.");
return;
}
TSharedPtr<FJsonObject> JSONPostFields = MakeShareable(new FJsonObject());
JSONPostFields->SetStringField("summary", PostData.Summary);
JSONPostFields->SetStringField("description", PostData.Comment);
JSONPostFields->SetStringField(this->MapNameCustomField, PostData.MapName);
JSONPostFields->SetStringField(this->MarkerLocationCustomField, PostData.MarkerLocation);
TSharedPtr<FJsonObject> Project = MakeShareable(new FJsonObject());
Project->SetStringField("key", this->ProjectKey);
JSONPostFields->SetObjectField("project", Project);
TSharedPtr<FJsonObject> IssueType = MakeShareable(new FJsonObject());
IssueType->SetStringField("name", GetDefault<UUnrealzillaGlobalSettings>()->JiraBugIssueType);
JSONPostFields->SetObjectField("issuetype", IssueType);
//const FString DefaultStatus = GetDefault<UUnrealzillaGlobalSettings>()->JiraDefaultStatus;
//if (!DefaultStatus.IsEmpty())
//{
// TSharedPtr<FJsonObject> Status = MakeShareable(new FJsonObject());
// Status->SetStringField("name", DefaultStatus);
// JSONPostFields->SetObjectField("status", Status);
//}
if (!GetDefault<UUnrealzillaGlobalSettings>()->JiraVersionField.IsEmpty() && !PostData.Version.IsEmpty())
{
TSharedPtr<FJsonObject> Version = MakeShareable(new FJsonObject());
Version->SetStringField("name", PostData.Version);
JSONPostFields->SetObjectField(this->VersionCustomField, Version);
}
if (!GetDefault<UUnrealzillaGlobalSettings>()->JiraPlatformField.IsEmpty() && !PostData.Platform.IsEmpty())
{
TSharedPtr<FJsonObject> PlatformCascade = MakeShareable(new FJsonObject());
TSharedPtr<FJsonObject> OSCascade = MakeShareable(new FJsonObject());
PlatformCascade->SetStringField("value", PostData.Platform);
if (!PostData.OS.IsEmpty())
{
OSCascade->SetStringField("value", PostData.OS);
PlatformCascade->SetObjectField("child", OSCascade);
}
JSONPostFields->SetObjectField(this->PlatformCustomField, PlatformCascade);
}
if (!GetDefault<UUnrealzillaGlobalSettings>()->JiraDepartmentField.IsEmpty() && !PostData.Component.IsEmpty())
{
TSharedPtr<FJsonObject> Category = MakeShareable(new FJsonObject());
Category->SetStringField("value", PostData.Component);
JSONPostFields->SetObjectField(this->DepartmentCustomField, Category);
}
if (!GetDefault<UUnrealzillaGlobalSettings>()->JiraSeverityField.IsEmpty() && !PostData.Severity.IsEmpty())
{
TSharedPtr<FJsonObject> Severity = MakeShareable(new FJsonObject());
Severity->SetStringField("value", PostData.Severity);
JSONPostFields->SetObjectField(this->SeverityCustomField, Severity);
}
TSharedPtr<FJsonObject> JSONPostObject = MakeShareable(new FJsonObject());
JSONPostObject->SetObjectField("fields", JSONPostFields);
FString PostJsonString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&PostJsonString);
FJsonSerializer::Serialize(JSONPostObject.ToSharedRef(), Writer);
FHttpModule &HttpModule = FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> PostRequest = HttpModule.CreateRequest();
PostRequest->SetVerb(TEXT("POST"));
PostRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
PostRequest->AppendToHeader(TEXT("Authorization"), TEXT("Basic ") + this->GenerateAuthString());
PostRequest->SetURL(this->FullURL + "api/2/issue");
PostRequest->SetContentAsString(PostJsonString);
PostRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerPOSTResponse);
PostRequest->ProcessRequest();
}
void UServerJiraAPI::ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
const FString &JSONResponse = Response->GetContentAsString();
TSharedPtr<FJsonObject> JSON = MakeShareable(new FJsonObject);
const TSharedRef<TJsonReader<>> &Reader = TJsonReaderFactory<>::Create(JSONResponse);
if (FJsonSerializer::Deserialize(Reader, JSON))
{
// I'm not sure what is meant to show up in the "errorMessages" array, but this is where we handle that.
const TArray<TSharedPtr<FJsonValue>> &ErrorMessageArray = JSON->GetArrayField("errorMessages");
if (!ErrorMessageArray.IsEmpty())
{
// Handle error messages here
}
const TSharedPtr<FJsonObject> &Errors = JSON->GetObjectField("errors");
TArray<FString> ErrorStringBuilder;
TArray<FString> ErrorKeys;
Errors->Values.GenerateKeyArray(ErrorKeys);
for (const FString &ErrorKey : ErrorKeys)
{
FStringFormatOrderedArguments Args;
Args.Add(FStringFormatArg(ErrorKey));
Args.Add(FStringFormatArg(Errors->GetStringField(ErrorKey)));
ErrorStringBuilder.Add(FString::Format(TEXT("{0}: {1}"), Args));
}
const FString &ErrorString = FString::Join(ErrorStringBuilder, TEXT("\n"));
if (!ErrorString.IsEmpty())
{
this->CreateError(ErrorString);
return;
}
//const int32 &NewBugID = FCString::Atoi(*JSON->GetStringField("id"));
//const FString &NewBugKey = JSON->GetStringField("key");
const FString &NewBugURI = JSON->GetStringField("self");
FHttpModule &HttpModule = FHttpModule::Get();
TSharedRef<IHttpRequest, ESPMode::ThreadSafe> NewBugRequest = HttpModule.CreateRequest();
NewBugRequest->SetVerb(TEXT("GET"));
NewBugRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
NewBugRequest->AppendToHeader(TEXT("Authorization"), TEXT("Basic ") + this->GenerateAuthString());
NewBugRequest->SetURL(NewBugURI);
NewBugRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerPOSTUpdateMarkerResponse);
NewBugRequest->ProcessRequest();
}
else
{
this->CreateError("Could not deserialise JSON.");
}
}
else
{
this->ServerConnectionError(Request->GetStatus());
}
}
void UServerJiraAPI::ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success)
{
if (Success)
{
const FString &JSONResponse = Response->GetContentAsString();
TSharedPtr<FJsonObject> JSON = MakeShareable(new FJsonObject);
const TSharedRef<TJsonReader<>> &Reader = TJsonReaderFactory<>::Create(JSONResponse);
if (FJsonSerializer::Deserialize(Reader, JSON))
{
if (JSON->HasTypedField<EJson::Object>("fields"))
{
const TSharedPtr<FJsonObject> &Fields = JSON->GetObjectField("fields");
const TSharedPtr<FJsonObject> &PlatformFields = Fields->GetObjectField(this->PlatformCustomField);
TArray<FUnrealzillaBugData> BugData;
FUnrealzillaBugData Bug;
Bug.ID = JSON->GetIntegerField("id");
Bug.Summary = Fields->GetStringField("summary");
Bug.Component = Fields->GetObjectField(this->DepartmentCustomField)->GetStringField("value");
Bug.MapName = Fields->GetStringField(this->MapNameCustomField);
Bug.MarkerLocation = Fields->GetStringField(this->MarkerLocationCustomField);
Bug.Platform = PlatformFields->GetStringField("value");
Bug.OperatingSystem = PlatformFields->GetObjectField("child")->GetStringField("value");
Bug.Severity = Fields->GetObjectField(this->SeverityCustomField)->GetStringField("value");
Bug.Status = Fields->GetObjectField("status")->GetStringField("name");
Bug.Resolution = Fields->GetStringField("resolution");
BugData.Add(Bug);
this->BugDataResponse.Execute(BugData);
}
}
else
{
this->CreateError("Could not deserialise JSON.");
}
}
else
{
this->ServerConnectionError(Request->GetStatus());
}
}
const FString UServerJiraAPI::GenerateAuthString() const
{
return FBase64::Encode(
GetDefault<UUnrealzillaGlobalSettings>()->JiraUsername +
":" +
GetDefault<UUnrealzillaGlobalSettings>()->JiraAPIKey
);
}

View File

@ -29,6 +29,8 @@ public:
FString ErrorMessage; FString ErrorMessage;
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
FString DocumentationLink; FString DocumentationLink;
UPROPERTY(BlueprintReadOnly)
bool bCancelForm = false;
}; };
USTRUCT(Blueprintable) USTRUCT(Blueprintable)
@ -45,7 +47,7 @@ public:
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
FString MapName; FString MapName;
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
FString MapLocation; FString MarkerLocation;
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
FString Platform; FString Platform;
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
@ -105,7 +107,7 @@ public:
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
FString MapName; FString MapName;
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
FString MapLocation; FString MarkerLocation;
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)
FString Summary; FString Summary;
UPROPERTY(BlueprintReadOnly) UPROPERTY(BlueprintReadOnly)

View File

@ -29,6 +29,8 @@ public:
UFUNCTION(BlueprintPure) UFUNCTION(BlueprintPure)
bool AreMarkersVisible() const { return !this->BugMarkers.IsEmpty(); } bool AreMarkersVisible() const { return !this->BugMarkers.IsEmpty(); }
void AddBugMarker(ABugMarkerActor *Marker) { this->BugMarkers.Add(Marker); }
void PrepareSubmissionFormData(); void PrepareSubmissionFormData();
void SubmitForm(const struct FUnrealzillaPostData &PostData); void SubmitForm(const struct FUnrealzillaPostData &PostData);

View File

@ -3,7 +3,6 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "BugzillaJSONStructs.h"
#include "GameFramework/Pawn.h" #include "GameFramework/Pawn.h"
#include "BugPlacerPawn.generated.h" #include "BugPlacerPawn.generated.h"

View File

@ -81,7 +81,7 @@ private:
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void ShowProcessingOverlayLoading(); void ShowProcessingOverlayLoading();
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void ShowProcessingOverlayMessage(const FString Message, const bool bExitAfterConfirm = false); void ShowProcessingOverlayMessage(const struct FUnrealzillaErrorData &Error);
UFUNCTION(BlueprintCallable) UFUNCTION(BlueprintCallable)
void HideProcessingOverlay(); void HideProcessingOverlay();

View File

@ -21,6 +21,8 @@ class UNREALZILLA_API UServerAPI : public UObject
GENERATED_BODY() GENERATED_BODY()
public: public:
virtual void Initialize() {}
virtual void ReturnListOfBugs(); virtual void ReturnListOfBugs();
virtual void PrepareForm(); virtual void PrepareForm();
virtual void SendFormData(const FUnrealzillaPostData &PostData); virtual void SendFormData(const FUnrealzillaPostData &PostData);
@ -46,7 +48,7 @@ protected:
void CreateError(const EErrorVerb &Verb, const FBugzillaJSONProductResponse &Data); void CreateError(const EErrorVerb &Verb, const FBugzillaJSONProductResponse &Data);
void CreateError(const EErrorVerb &Verb, const FBugzillaJSONFieldResponse &Data); void CreateError(const EErrorVerb &Verb, const FBugzillaJSONFieldResponse &Data);
void CreateError(const EErrorVerb &Verb, const FBugzillaJSONBugResponse &Data); void CreateError(const EErrorVerb &Verb, const FBugzillaJSONBugResponse &Data);
void CreateError(const FString &ErrorMessage); void CreateError(const FString &ErrorMessage, const bool bCancelForm = false);
static const FString URLBuilder(const FString &Server, const FString &Path); static const FString URLBuilder(const FString &Server, const FString &Path);
static const FString FormatQueryString(const TMap<FString, FString> &QueryData); static const FString FormatQueryString(const TMap<FString, FString> &QueryData);

View File

@ -16,7 +16,7 @@ class UNREALZILLA_API UServerBugzillaAPI : public UServerAPI
GENERATED_BODY() GENERATED_BODY()
public: public:
UServerBugzillaAPI(); virtual void Initialize() override;
virtual void ReturnListOfBugs() override; virtual void ReturnListOfBugs() override;
virtual void PrepareForm() override; virtual void PrepareForm() override;

View File

@ -16,20 +16,39 @@ class UNREALZILLA_API UServerJiraAPI : public UServerAPI
GENERATED_BODY() GENERATED_BODY()
public: public:
UServerJiraAPI(); virtual void Initialize() override;
virtual void ReturnListOfBugs() override; virtual void ReturnListOfBugs() override;
virtual void PrepareForm() override; virtual void PrepareForm() override;
virtual void SendFormData(const FUnrealzillaPostData &PostData) override; virtual void SendFormData(const FUnrealzillaPostData &PostData) override;
private: private:
void ListOfBoardsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ListOfFieldsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ListOfBugsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); void ListOfBugsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); void ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); void ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ServerProductInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); void ServerFieldOptionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ServerSeverityInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); void ServerVersionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ServerPlatformInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success);
void CheckIfAllFormResponsesAreIn(); void CheckIfAllFormResponsesAreIn();
const FString GenerateAuthString() const;
int32 BoardID = -1;
int32 ProjectID = -1;
FString ProjectKey;
FString ProjectAvatarURI;
FString MapNameCustomField;
FString MarkerLocationCustomField;
FString DepartmentCustomField;
FString SeverityCustomField;
FString PlatformCustomField;
FString VersionCustomField;
uint8 FieldListsCompletion = 0;
uint8 FieldListsCompletionMax = 0;
}; };

View File

@ -36,12 +36,12 @@ public:
UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly, Category="Bug Placement", meta=(DisplayName="Arbitrary Placement Distance")) UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly, Category="Bug Placement", meta=(DisplayName="Arbitrary Placement Distance"))
float ArbitraryBugPlacementDistance = 250.0f; float ArbitraryBugPlacementDistance = 250.0f;
// The status to use when filing a new bug. A status such as "UNCONFIRMED" is suggested.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting")
FString DefaultStatus;
// The viewport depth of the bug report interface widget. // The viewport depth of the bug report interface widget.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting") UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting")
int32 BugReportWidgetDepth = 10000; int32 BugReportWidgetDepth = 10000;
// The status to use when filing a new bug. Usually the default is sufficient.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Bugzilla", meta=(DisplayName="Default Status"))
FString BugzillaDefaultStatus = "UNCONFIRMED";
// The Bugzilla server where bugs will be posted. // The Bugzilla server where bugs will be posted.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Bugzilla", meta=(DisplayName="Server")) UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Bugzilla", meta=(DisplayName="Server"))
FString BugzillaSubmissionServer; FString BugzillaSubmissionServer;
@ -64,13 +64,43 @@ public:
// The name of the project for which bugs will be posted. // The name of the project for which bugs will be posted.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Project Name")) UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Project Name"))
FString JiraProjectName; FString JiraProjectName;
// The username to use when posting bugs. // The username to use when posting bugs. Should be the user's full email address.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Username")) UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Email"))
FString JiraUsername; FString JiraUsername;
// The API key to use when posting bugs. Using the account password should work here, but it is // The API key to use when posting bugs. Using the account password might work here, but it is
// highly recommended that you generate a proper API key in the Atlassian account of the above user. // highly recommended that you generate a proper API key in the Atlassian account of the above user.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="API Key")) UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="API Key"))
FString JiraAPIKey; FString JiraAPIKey;
// Name of the Jira issue type used for bugs. Should simply be "Bug", but if you use something different
// it can be changed here.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Bug Issue Type"))
FString JiraBugIssueType = "Bug";
// Name of the Jira custom field where the map name should be stored. This field is required, and must be
// configured in Jira. The custom field must be a Text Field.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Map Name Field"))
FString JiraMapNameField;
// Name of the Jira custom field where the bug location should be stored. This field is required, and must
// be configured in Jira. The custom field must be a Text Field.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Marker Location Field"))
FString JiraMarkerLocationField;
// Name of the Jira custom field where the department name should be stored. This serves a similar function to
// "Component" in Bugzilla; it's meant to indicate what department in a company should be responsible for fixing
// the bug. The custom field must be a single-choice select list.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira|Optional Fields", meta=(DisplayName="Department Field"))
FString JiraDepartmentField;
// Name of the Jira custom field where the severity should be stored. The custom field must be a single-choice select list.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira|Optional Fields", meta=(DisplayName="Severity Field"))
FString JiraSeverityField;
// Name of the Jira custom field where the hardware and OS used to find the bug should be stored. The custom field
// must be a cascading select list, with the first dropdown being hardware, and the second being the operating system.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira|Optional Fields", meta=(DisplayName="Platform Field"))
FString JiraPlatformField;
// Name of the Jira custom field where the game's version number should be stored. The custom field must be a Version
// select list, and "Releases" must be enabled in your project features and configured with the version numbers you
// wish to track bugs for. Unrealzilla will automatically fill this in with the version number from the project settings,
// and will simply not fill this field in if it can't find a matching version number configured for the project.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira|Optional Fields", meta=(DisplayName="Version Field"))
FString JiraVersionField;
// Whether to show unresolved bugs when displaying bug report markers. // Whether to show unresolved bugs when displaying bug report markers.
UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Markers|Unresolved") UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Markers|Unresolved")