diff --git a/Source/Unrealzilla/Private/API/BugzillaJSONStructs.h b/Source/Unrealzilla/Private/API/BugzillaJSONStructs.h index ff47a82..f8128b2 100644 --- a/Source/Unrealzilla/Private/API/BugzillaJSONStructs.h +++ b/Source/Unrealzilla/Private/API/BugzillaJSONStructs.h @@ -4,7 +4,7 @@ #include "CoreMinimal.h" -//#include "BugzillaJSONStructs.generated.h" +#include "BugzillaJSONStructs.generated.h" /** diff --git a/Source/Unrealzilla/Private/API/JiraJSONStructs.h b/Source/Unrealzilla/Private/API/JiraJSONStructs.h index 5f97de1..651caaa 100644 --- a/Source/Unrealzilla/Private/API/JiraJSONStructs.h +++ b/Source/Unrealzilla/Private/API/JiraJSONStructs.h @@ -4,4 +4,4 @@ #include "CoreMinimal.h" -//#include "JiraJSONStructs.generated.h" +#include "JiraJSONStructs.generated.h" diff --git a/Source/Unrealzilla/Private/BugMarkerSubsystem.cpp b/Source/Unrealzilla/Private/BugMarkerSubsystem.cpp index 9f07074..eca8468 100644 --- a/Source/Unrealzilla/Private/BugMarkerSubsystem.cpp +++ b/Source/Unrealzilla/Private/BugMarkerSubsystem.cpp @@ -21,13 +21,17 @@ void UBugMarkerSubsystem::Initialize(FSubsystemCollectionBase &Collection) this->ServerAPI = NewObject(this); break; case EBugReportPlatform::Jira: - // this->ServerAPI = NewObject(this); - // break; + this->ServerAPI = NewObject(this); + break; default: this->ServerAPI = NewObject(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")); } @@ -79,6 +83,7 @@ void UBugMarkerSubsystem::HideBugMarkers() } } + void UBugMarkerSubsystem::PrepareSubmissionFormData() { this->ServerAPI->FormDataResponse.BindUObject(this, &UBugMarkerSubsystem::FormPrepResponseCallback); @@ -106,7 +111,7 @@ void UBugMarkerSubsystem::LoadNewBatch() const FUnrealzillaBugData BugData = this->BugBatch[0]; FString LocationString, UpVectorString; - BugData.MapLocation.Split(":", &LocationString, &UpVectorString); + BugData.MarkerLocation.Split(":", &LocationString, &UpVectorString); FString LocationX, LocationY, LocationZ; LocationString.Split(",", &LocationX, &LocationY); diff --git a/Source/Unrealzilla/Private/BugSubmissionForm.cpp b/Source/Unrealzilla/Private/BugSubmissionForm.cpp index 9c0e3f7..05b0c06 100644 --- a/Source/Unrealzilla/Private/BugSubmissionForm.cpp +++ b/Source/Unrealzilla/Private/BugSubmissionForm.cpp @@ -36,7 +36,7 @@ void UBugSubmissionForm::NativeOnInitialized() { this->ShowProcessingOverlayLoading(); - UBugMarkerSubsystem *BugMarkerSubsystem = UGameplayStatics::GetPlayerController(this, 0)->GetLocalPlayer()->GetSubsystem(); + UBugMarkerSubsystem *BugMarkerSubsystem = UBugMarkerSubsystem::GetBugMarkerSubsystem(this); BugMarkerSubsystem->FormPrepResponse.BindUObject(this, &UBugSubmissionForm::PrepareFormData); BugMarkerSubsystem->PrepareSubmissionFormData(); @@ -95,13 +95,13 @@ void UBugSubmissionForm::ShowProcessingOverlayLoading() 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->ProcessingRequestThrobber->SetVisibility(ESlateVisibility::Collapsed); this->ProcessingRequestErrorBox->SetVisibility(ESlateVisibility::Visible); - if (bExitAfterConfirm) + if (Error.bCancelForm) { this->ProcessingRequestErrorButton->OnClicked().AddUObject(this, &UBugSubmissionForm::CancelForm); } @@ -123,12 +123,13 @@ void UBugSubmissionForm::SubmitForm() PostData.Component = this->ComponentButton->GetText().ToString(); PostData.Severity = this->SeverityButton->GetText().ToString(); PostData.MapName = this->MapName.ToString(); - PostData.MapLocation = this->MarkerLocation.ToString(); + PostData.MarkerLocation = this->MarkerLocation.ToString(); PostData.Summary = this->SummaryEntryBox->GetText().ToString(); PostData.Comment = this->CommentEntryBox->GetText().ToString(); - UBugMarkerSubsystem *BugMarkerSubsystem = UGameplayStatics::GetPlayerController(this, 0)->GetLocalPlayer()->GetSubsystem(); + UBugMarkerSubsystem *BugMarkerSubsystem = UBugMarkerSubsystem::GetBugMarkerSubsystem(this); BugMarkerSubsystem->FormPostResponse.BindUObject(this, &UBugSubmissionForm::UpdateReportMarker); + BugMarkerSubsystem->ErrorResponse.BindUObject(this, &UBugSubmissionForm::ShowProcessingOverlayMessage); BugMarkerSubsystem->SubmitForm(PostData); this->OnFormSubmit.ExecuteIfBound(); @@ -136,7 +137,16 @@ void UBugSubmissionForm::SubmitForm() 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(); } @@ -159,5 +169,5 @@ void UBugSubmissionForm::CloseForm() void UBugSubmissionForm::ErrorResponseCallback(const FUnrealzillaErrorData &Error) { - this->ShowProcessingOverlayMessage(Error.ErrorMessage); + this->ShowProcessingOverlayMessage(Error); } diff --git a/Source/Unrealzilla/Private/ServerAPI.cpp b/Source/Unrealzilla/Private/ServerAPI.cpp index 6cefa79..1345139 100644 --- a/Source/Unrealzilla/Private/ServerAPI.cpp +++ b/Source/Unrealzilla/Private/ServerAPI.cpp @@ -30,20 +30,17 @@ void UServerAPI::ServerConnectionError(const EHttpRequestStatus::Type Status) { switch (Status) { case EHttpRequestStatus::Failed_ConnectionError: - this->CreateError("Unable to connect to the server"); - //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); + this->CreateError("There was an error connecting to the server. Please retry.", true); break; case EHttpRequestStatus::Failed: - this->CreateError("Connection failed"); - //this->ShowProcessingOverlayMessage("Connection failed", true); + this->CreateError("Connection to the server completed but then failed.", true); + break; + case EHttpRequestStatus::NotStarted: + this->CreateError("Connection was not started."); + break; + case EHttpRequestStatus::Processing: + this->CreateError("Connection is still being processed."); 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; this->ErrorResponse.Execute(Error); } -void UServerAPI::CreateError(const FString &ErrorMessage) +void UServerAPI::CreateError(const FString &ErrorMessage, const bool bCancelForm) { FUnrealzillaErrorData Error; Error.ErrorVerb = EErrorVerb::None; Error.ErrorMessage = ErrorMessage; + Error.bCancelForm = bCancelForm; this->ErrorResponse.Execute(Error); } diff --git a/Source/Unrealzilla/Private/ServerBugzillaAPI.cpp b/Source/Unrealzilla/Private/ServerBugzillaAPI.cpp index 3ebc8f6..c12bdf4 100644 --- a/Source/Unrealzilla/Private/ServerBugzillaAPI.cpp +++ b/Source/Unrealzilla/Private/ServerBugzillaAPI.cpp @@ -10,7 +10,7 @@ #include "Kismet/GameplayStatics.h" -UServerBugzillaAPI::UServerBugzillaAPI() +void UServerBugzillaAPI::Initialize() { this->FullURL = UServerAPI::URLBuilder( GetDefault()->BugzillaSubmissionServer, @@ -74,7 +74,7 @@ void UServerBugzillaAPI::ListOfBugsResponse(FHttpRequestPtr Request, FHttpRespon Bug.Summary = BugzillaData.summary; Bug.Component = BugzillaData.component; Bug.MapName = BugzillaData.cf_mapname; - Bug.MapLocation = BugzillaData.cf_location; + Bug.MarkerLocation = BugzillaData.cf_location; Bug.Platform = BugzillaData.platform; Bug.OperatingSystem = BugzillaData.op_sys; Bug.Severity = BugzillaData.severity; @@ -140,7 +140,7 @@ void UServerBugzillaAPI::SendFormData(const FUnrealzillaPostData &PostData) TMap QueryData; QueryData.Add("api_key", GetDefault()->BugzillaAPIKey); - const FString DefaultStatus = GetDefault()->DefaultStatus; + const FString DefaultStatus = GetDefault()->BugzillaDefaultStatus; FBugzillaJSONPostBug PostDataJSON; PostDataJSON.product = GetDefault()->BugzillaProductName; @@ -150,7 +150,7 @@ void UServerBugzillaAPI::SendFormData(const FUnrealzillaPostData &PostData) PostDataJSON.component = PostData.Component; PostDataJSON.severity = PostData.Severity; PostDataJSON.cf_mapname = PostData.MapName; - PostDataJSON.cf_location = PostData.MapLocation; + PostDataJSON.cf_location = PostData.MarkerLocation; PostDataJSON.summary = PostData.Summary; PostDataJSON.description = PostData.Comment; if (!DefaultStatus.IsEmpty()) @@ -278,7 +278,7 @@ void UServerBugzillaAPI::ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, 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.MarkerLocation = ResponseData.bugs[0].cf_location; Bug.Platform = ResponseData.bugs[0].platform; Bug.OperatingSystem = ResponseData.bugs[0].op_sys; Bug.Severity = ResponseData.bugs[0].severity; diff --git a/Source/Unrealzilla/Private/ServerJiraAPI.cpp b/Source/Unrealzilla/Private/ServerJiraAPI.cpp index 5fdcdcc..692743e 100644 --- a/Source/Unrealzilla/Private/ServerJiraAPI.cpp +++ b/Source/Unrealzilla/Private/ServerJiraAPI.cpp @@ -10,437 +10,332 @@ #include "Kismet/GameplayStatics.h" -UServerJiraAPI::UServerJiraAPI() +void UServerJiraAPI::Initialize() { this->FullURL = UServerAPI::URLBuilder( GetDefault()->JiraSubmissionServer, GetDefault()->JiraRESTURI ); + + FHttpModule &HttpModule = FHttpModule::Get(); + + // Get board data + TSharedRef 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 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 JSON = MakeShareable(new FJsonObject); + const TSharedRef> &Reader = TJsonReaderFactory<>::Create(JSONResponse); + if (FJsonSerializer::Deserialize(Reader, JSON)) + { + const TArray> &BoardsArray = JSON->GetArrayField("values"); + for (const TSharedPtr &BoardValue : BoardsArray) + { + const TSharedPtr &Board = BoardValue->AsObject(); + const TSharedPtr &Location = Board->GetObjectField("location"); + if (Location->GetStringField("projectName") == GetDefault()->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 JSON = MakeShareable(new FJsonObject); + const TSharedRef> &Reader = TJsonReaderFactory<>::Create(JSONResponse); + if (FJsonSerializer::Deserialize(Reader, JSON)) + { + const TArray> &FieldArray = JSON->GetArrayField("fields"); + for (const TSharedPtr &FieldValue : FieldArray) + { + const TSharedPtr &Field = FieldValue->AsObject(); + const FString &Name = Field->GetStringField("name"); + const FString &Key = Field->GetStringField("key"); + if (Name == GetDefault()->JiraMapNameField) + { + this->MapNameCustomField = Key; + } + else if (Name == GetDefault()->JiraMarkerLocationField) + { + this->MarkerLocationCustomField = Key; + } + else if (Name == GetDefault()->JiraDepartmentField) + { + this->DepartmentCustomField = Key; + } + else if (Name == GetDefault()->JiraSeverityField) + { + this->SeverityCustomField = Key; + } + else if (Name == GetDefault()->JiraPlatformField) + { + this->PlatformCustomField = Key; + } + else if (Name == GetDefault()->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() { - const FString FullURL = GetDefault()->JiraSubmissionServer + "/rest"; - - TArray StatusQueries; - if (GetDefault()->bShowUnresolvedBugs) - { - for (const FString Unresolved : GetDefault()->UnresolvedStatuses) - { - StatusQueries.Add("status=" + Unresolved); - } - } - if (GetDefault()->bShowInProgressBugs) - { - for (const FString InProgress : GetDefault()->InProgressStatuses) - { - StatusQueries.Add("status=" + InProgress); - } - } - if (GetDefault()->bShowResolvedBugs) - { - for (const FString Resolved : GetDefault()->ResolvedStatuses) - { - StatusQueries.Add("status=" + Resolved); - } - } - StatusQueries.Add("cf_mapname=" + this->GetWorld()->GetMapName().RightChop(this->GetWorld()->StreamingLevelsPrefix.Len())); - StatusQueries.Add("api_key=" + GetDefault()->JiraAPIKey); - const FString QueryString = FString::Join(StatusQueries, TEXT("&")); - FHttpModule &HttpModule = FHttpModule::Get(); TSharedRef SeverityRequest = HttpModule.CreateRequest(); SeverityRequest->SetVerb(TEXT("GET")); 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->ProcessRequest(); } - void UServerJiraAPI::ListOfBugsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) { if (Success) { - FBugzillaJSONBugResponse ResponseData; - FString JSONResponse = Response->GetContentAsString(); - FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + TArray BugData; - if (!ResponseData.error) + const FString JSONResponse = Response->GetContentAsString(); + + TSharedPtr JSON = MakeShareable(new FJsonObject); + const TSharedRef> &Reader = TJsonReaderFactory<>::Create(JSONResponse); + if (FJsonSerializer::Deserialize(Reader, JSON)) { - TArray BugData; - for (const FBugzillaJSONBugData &BugzillaData : ResponseData.bugs) + const TArray> &IssuesArray = JSON->GetArrayField("issues"); + for (const TSharedPtr &IssueValue : IssuesArray) { - FUnrealzillaBugData Bug; - Bug.ID = BugzillaData.id; - Bug.Summary = BugzillaData.summary; - Bug.Component = BugzillaData.component; - Bug.MapName = BugzillaData.cf_mapname; - Bug.MapLocation = BugzillaData.cf_location; - Bug.Platform = BugzillaData.platform; - Bug.OperatingSystem = BugzillaData.op_sys; - Bug.Severity = BugzillaData.severity; - Bug.Status = BugzillaData.status; - Bug.Resolution = BugzillaData.resolution; - Bug.DuplicateOf = BugzillaData.dupe_of; - Bug.bIsOpen = BugzillaData.is_open; - BugData.Add(Bug); + const TSharedPtr &Issue = IssueValue->AsObject(); + const TSharedPtr &Fields = Issue->GetObjectField("fields"); + const TSharedPtr &Status = Fields->GetObjectField("status"); + const FString StatusName = Status->GetStringField("name"); + + if ((GetDefault()->bShowUnresolvedBugs && GetDefault()->UnresolvedStatuses.Contains(StatusName)) || + (GetDefault()->bShowInProgressBugs && GetDefault()->InProgressStatuses.Contains(StatusName)) || + (GetDefault()->bShowResolvedBugs && GetDefault()->ResolvedStatuses.Contains(StatusName))) + { + const TSharedPtr &Priority = Fields->GetObjectField("priority"); + + FUnrealzillaBugData Bug; + Bug.ID = Issue->GetNumberField("id"); + 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 { - this->CreateError(EErrorVerb::GET, ResponseData); + this->CreateError("Could not deserialise JSON."); } + + this->BugDataResponse.Execute(BugData); + } + else + { + this->ServerConnectionError(Request->GetStatus()); } } void UServerJiraAPI::PrepareForm() { - const FString FullURL = GetDefault()->JiraSubmissionServer + "/rest"; - // Assemble query data into key:value pairs TMap QueryData; - QueryData.Add("api_key", GetDefault()->JiraAPIKey); + QueryData.Add("issuetypeNames", GetDefault()->JiraBugIssueType); + QueryData.Add("expand", "projects.issuetypes.fields"); const FString QueryString = UServerAPI::FormatQueryString(QueryData); - // Query the server for information about the current product - FHttpModule &HttpModule = FHttpModule::Get(); - TSharedRef ProductRequest = HttpModule.CreateRequest(); - ProductRequest->SetVerb(TEXT("GET")); - ProductRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json")); - ProductRequest->SetURL(FullURL + "/product/" + GetDefault()->JiraProjectName + "?" + QueryString); - ProductRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerProductInfoResponse); - ProductRequest->ProcessRequest(); - - // Send a second query to retrieve severity options - TSharedRef 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 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 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()->JiraSubmissionServer + "/rest"; - const FString RequestURL = "/bug"; - - // Assemble query data into key:value pairs - TMap QueryData; - QueryData.Add("api_key", GetDefault()->JiraAPIKey); - - const FString DefaultStatus = GetDefault()->DefaultStatus; - - FBugzillaJSONPostBug PostDataJSON; - PostDataJSON.product = GetDefault()->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); + // Keep track of how many of these tasks have completed so we can check their completion status later. + this->FieldListsCompletion = 0; + this->FieldListsCompletionMax = 2; FHttpModule &HttpModule = FHttpModule::Get(); - TSharedRef Request = HttpModule.CreateRequest(); - Request->SetVerb(TEXT("POST")); - Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); - Request->SetURL(FullURL + RequestURL + "?" + UServerAPI::FormatQueryString(QueryData)); - Request->SetContentAsString(PostJsonString); - Request->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerPOSTResponse); - Request->ProcessRequest(); + + // Send a query to retrieve field options + TSharedRef 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/issue/createmeta?" + QueryString); + FieldsRequest->OnProcessRequestComplete().BindUObject(this, &UServerJiraAPI::ServerFieldOptionsResponse); + FieldsRequest->ProcessRequest(); + + // Send a second query to retrieve version numbers + TSharedRef 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::ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +void UServerJiraAPI::ServerFieldOptionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) { if (Success) { - FBugzillaJSONPostResponse ResponseData; - FString JSONResponse = Response->GetContentAsString(); - FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + const FString &JSONResponse = Response->GetContentAsString(); - if (ResponseData.error) + TSharedPtr JSON = MakeShareable(new FJsonObject); + const TSharedRef> &Reader = TJsonReaderFactory<>::Create(JSONResponse); + if (FJsonSerializer::Deserialize(Reader, JSON)) { - this->CreateError(EErrorVerb::POST, ResponseData); - } - 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()->JiraSubmissionServer + "/rest"; - - TArray StatusQueries; - StatusQueries.Add("id=" + FString::FromInt(ResponseData.id)); - if (GetDefault()->bShowUnresolvedBugs) + const TArray> &ProjectsArray = JSON->GetArrayField("projects"); + for (const TSharedPtr &ProjectValue : ProjectsArray) { - for (const FString Unresolved : GetDefault()->UnresolvedStatuses) + const TSharedPtr &Project = ProjectValue->AsObject(); + if (Project->GetStringField("name") == GetDefault()->JiraProjectName) { - StatusQueries.Add("status=" + Unresolved); - } - } - if (GetDefault()->bShowInProgressBugs) - { - for (const FString InProgress : GetDefault()->InProgressStatuses) - { - StatusQueries.Add("status=" + InProgress); - } - } - if (GetDefault()->bShowResolvedBugs) - { - for (const FString Resolved : GetDefault()->ResolvedStatuses) - { - StatusQueries.Add("status=" + Resolved); - } - } - StatusQueries.Add("cf_mapname=" + this->GetWorld()->GetMapName().RightChop(this->GetWorld()->StreamingLevelsPrefix.Len())); - StatusQueries.Add("api_key=" + GetDefault()->JiraAPIKey); - const FString QueryString = FString::Join(StatusQueries, TEXT("&")); - - FHttpModule &HttpModule = FHttpModule::Get(); - TSharedRef 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 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()->JiraProjectName) - { - for (const FBugzillaJSONComponentData &ComponentData : ProductData.components) + const TArray> &IssueTypes = Project->GetArrayField("issuetypes"); + for (const TSharedPtr &IssueTypeValue : IssueTypes) { - this->ComponentsList.Add(ComponentData.name); + const TSharedPtr &IssueType = IssueTypeValue->AsObject(); + const TSharedPtr &Fields = IssueType->GetObjectField("fields"); + + // After all that boilerplate code, finally get all available field options here + { + // Components + const TSharedPtr &Department = Fields->GetObjectField(this->DepartmentCustomField); + const TArray> &AllowedDepartmentsArray = Department->GetArrayField("allowedValues"); + for (const TSharedPtr &AllowedDepartmentsValue : AllowedDepartmentsArray) + { + const TSharedPtr &Value = AllowedDepartmentsValue->AsObject(); + this->ComponentsList.Add(Value->GetStringField("value")); + } + + // Bug severity + const TSharedPtr &Severity = Fields->GetObjectField(this->SeverityCustomField); + const TArray> &AllowedSeveritiesArray = Severity->GetArrayField("allowedValues"); + for (const TSharedPtr &AllowedSeveritiesValue : AllowedSeveritiesArray) + { + const TSharedPtr &Value = AllowedSeveritiesValue->AsObject(); + this->SeverityList.Add(Value->GetStringField("value")); + } + + // Platform + const TSharedPtr &Platform = Fields->GetObjectField(this->PlatformCustomField); + const TArray> &PlatformsArray = Platform->GetArrayField("allowedValues"); + for (const TSharedPtr &PlatformsValue : PlatformsArray) + { + const TSharedPtr &Value = PlatformsValue->AsObject(); + this->PlatformsList.Add(Value->GetStringField("value")); + + // Also get children and add them to the OS list here + const TArray> &ChildrenArray = Value->GetArrayField("children"); + for (const TSharedPtr &ChildValue : ChildrenArray) + { + const TSharedPtr &Child = ChildValue->AsObject(); + this->OSList.AddUnique(Child->GetStringField("value")); + } + } + } } - for (const FBugzillaJSONVersionData &VersionData : ProductData.versions) - { - this->VersionsList.Add(VersionData.name); - } - } - this->CheckIfAllFormResponsesAreIn(); - } - else - { - FStringFormatOrderedArguments Args; - Args.Add(FStringFormatArg(GetDefault()->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); + break; } } this->CheckIfAllFormResponsesAreIn(); } + else + { + this->CreateError("Could not deserialise JSON."); + } } else { this->ServerConnectionError(Request->GetStatus()); } } - -void UServerJiraAPI::ServerPlatformInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) +void UServerJiraAPI::ServerVersionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success) { if (Success) { - FBugzillaJSONFieldResponse ResponseData; - FString JSONResponse = Response->GetContentAsString(); - FJsonObjectConverter::JsonObjectStringToUStruct(JSONResponse, &ResponseData); + const FString &JSONResponse = Response->GetContentAsString(); - if (ResponseData.error) + TSharedPtr JSON = MakeShareable(new FJsonObject); + const TSharedRef> &Reader = TJsonReaderFactory<>::Create(JSONResponse); + if (FJsonSerializer::Deserialize(Reader, JSON)) { - //this->ShowProcessingOverlayMessage(ResponseData.message); - this->CreateError(EErrorVerb::GET, ResponseData); - } - else - { - if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "rep_platform") + const TArray> &ValuesArray = JSON->GetArrayField("values"); + for (const TSharedPtr &ValueObject : ValuesArray) { - for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values) - { - this->PlatformsList.Add(FieldValue.name); - } + const TSharedPtr &Value = ValueObject->AsObject(); + this->VersionsList.Add(Value->GetStringField("name")); } 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 { - if (!ResponseData.fields.IsEmpty() && ResponseData.fields[0].name == "op_sys") - { - for (const FBugzillaJSONFieldValueData &FieldValue : ResponseData.fields[0].values) - { - this->OSList.Add(FieldValue.name); - } - } - - this->CheckIfAllFormResponsesAreIn(); + this->CreateError("Could not deserialise JSON."); } } else @@ -448,12 +343,14 @@ void UServerJiraAPI::ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponse this->ServerConnectionError(Request->GetStatus()); } } - void UServerJiraAPI::CheckIfAllFormResponsesAreIn() { - if (!this->ComponentsList.IsEmpty() && !this->VersionsList.IsEmpty() && !this->SeverityList.IsEmpty() && - !this->PlatformsList.IsEmpty() && !this->OSList.IsEmpty()) + this->FieldListsCompletion++; + + if (this->FieldListsCompletion == this->FieldListsCompletionMax) { + this->FieldListsCompletion = this->FieldListsCompletionMax = 0; + FUnrealzillaFormPrepData Data; Data.ComponentsList = this->ComponentsList; @@ -461,17 +358,13 @@ void UServerJiraAPI::CheckIfAllFormResponsesAreIn() Data.VersionsList = this->VersionsList; Data.PlatformsList = this->PlatformsList; Data.OSList = this->OSList; - + // Find a default version number to use if possible const FString GameVersion = UServerAPI::GetGameVersion(); if (this->VersionsList.Contains(GameVersion)) { Data.DetectedVersion = GameVersion; } - else if (this->VersionsList.Contains("unspecified")) - { - Data.DetectedVersion = "unspecified"; - } else if (this->VersionsList.Contains("Latest")) { Data.DetectedVersion = "Latest"; @@ -526,3 +419,196 @@ void UServerJiraAPI::CheckIfAllFormResponsesAreIn() this->OSList.Empty(); } } + +void UServerJiraAPI::SendFormData(const FUnrealzillaPostData &PostData) +{ + if (PostData.Summary.IsEmpty()) + { + this->CreateError("You must provide a summary."); + return; + } + + TSharedPtr 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 Project = MakeShareable(new FJsonObject()); + Project->SetStringField("key", this->ProjectKey); + JSONPostFields->SetObjectField("project", Project); + + TSharedPtr IssueType = MakeShareable(new FJsonObject()); + IssueType->SetStringField("name", GetDefault()->JiraBugIssueType); + JSONPostFields->SetObjectField("issuetype", IssueType); + + //const FString DefaultStatus = GetDefault()->JiraDefaultStatus; + //if (!DefaultStatus.IsEmpty()) + //{ + // TSharedPtr Status = MakeShareable(new FJsonObject()); + // Status->SetStringField("name", DefaultStatus); + // JSONPostFields->SetObjectField("status", Status); + //} + + if (!GetDefault()->JiraVersionField.IsEmpty() && !PostData.Version.IsEmpty()) + { + TSharedPtr Version = MakeShareable(new FJsonObject()); + Version->SetStringField("name", PostData.Version); + JSONPostFields->SetObjectField(this->VersionCustomField, Version); + } + + if (!GetDefault()->JiraPlatformField.IsEmpty() && !PostData.Platform.IsEmpty()) + { + TSharedPtr PlatformCascade = MakeShareable(new FJsonObject()); + TSharedPtr 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()->JiraDepartmentField.IsEmpty() && !PostData.Component.IsEmpty()) + { + TSharedPtr Category = MakeShareable(new FJsonObject()); + Category->SetStringField("value", PostData.Component); + JSONPostFields->SetObjectField(this->DepartmentCustomField, Category); + } + + if (!GetDefault()->JiraSeverityField.IsEmpty() && !PostData.Severity.IsEmpty()) + { + TSharedPtr Severity = MakeShareable(new FJsonObject()); + Severity->SetStringField("value", PostData.Severity); + JSONPostFields->SetObjectField(this->SeverityCustomField, Severity); + } + + TSharedPtr JSONPostObject = MakeShareable(new FJsonObject()); + JSONPostObject->SetObjectField("fields", JSONPostFields); + FString PostJsonString; + TSharedRef> Writer = TJsonWriterFactory<>::Create(&PostJsonString); + FJsonSerializer::Serialize(JSONPostObject.ToSharedRef(), Writer); + + FHttpModule &HttpModule = FHttpModule::Get(); + TSharedRef 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 JSON = MakeShareable(new FJsonObject); + const TSharedRef> &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> &ErrorMessageArray = JSON->GetArrayField("errorMessages"); + if (!ErrorMessageArray.IsEmpty()) + { + // Handle error messages here + } + + const TSharedPtr &Errors = JSON->GetObjectField("errors"); + TArray ErrorStringBuilder; + TArray 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 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 JSON = MakeShareable(new FJsonObject); + const TSharedRef> &Reader = TJsonReaderFactory<>::Create(JSONResponse); + if (FJsonSerializer::Deserialize(Reader, JSON)) + { + if (JSON->HasTypedField("fields")) + { + const TSharedPtr &Fields = JSON->GetObjectField("fields"); + const TSharedPtr &PlatformFields = Fields->GetObjectField(this->PlatformCustomField); + + TArray 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()->JiraUsername + + ":" + + GetDefault()->JiraAPIKey + ); +} diff --git a/Source/Unrealzilla/Public/API/UnrealzillaBugDataStructs.h b/Source/Unrealzilla/Public/API/UnrealzillaBugDataStructs.h index 48868d0..e00d479 100644 --- a/Source/Unrealzilla/Public/API/UnrealzillaBugDataStructs.h +++ b/Source/Unrealzilla/Public/API/UnrealzillaBugDataStructs.h @@ -29,6 +29,8 @@ public: FString ErrorMessage; UPROPERTY(BlueprintReadOnly) FString DocumentationLink; + UPROPERTY(BlueprintReadOnly) + bool bCancelForm = false; }; USTRUCT(Blueprintable) @@ -45,7 +47,7 @@ public: UPROPERTY(BlueprintReadOnly) FString MapName; UPROPERTY(BlueprintReadOnly) - FString MapLocation; + FString MarkerLocation; UPROPERTY(BlueprintReadOnly) FString Platform; UPROPERTY(BlueprintReadOnly) @@ -105,7 +107,7 @@ public: UPROPERTY(BlueprintReadOnly) FString MapName; UPROPERTY(BlueprintReadOnly) - FString MapLocation; + FString MarkerLocation; UPROPERTY(BlueprintReadOnly) FString Summary; UPROPERTY(BlueprintReadOnly) diff --git a/Source/Unrealzilla/Public/BugMarkerSubsystem.h b/Source/Unrealzilla/Public/BugMarkerSubsystem.h index 1eaeac6..5abc0a8 100644 --- a/Source/Unrealzilla/Public/BugMarkerSubsystem.h +++ b/Source/Unrealzilla/Public/BugMarkerSubsystem.h @@ -29,6 +29,8 @@ public: UFUNCTION(BlueprintPure) bool AreMarkersVisible() const { return !this->BugMarkers.IsEmpty(); } + void AddBugMarker(ABugMarkerActor *Marker) { this->BugMarkers.Add(Marker); } + void PrepareSubmissionFormData(); void SubmitForm(const struct FUnrealzillaPostData &PostData); diff --git a/Source/Unrealzilla/Public/BugPlacerPawn.h b/Source/Unrealzilla/Public/BugPlacerPawn.h index 9b92837..69b68cd 100644 --- a/Source/Unrealzilla/Public/BugPlacerPawn.h +++ b/Source/Unrealzilla/Public/BugPlacerPawn.h @@ -3,7 +3,6 @@ #pragma once #include "CoreMinimal.h" -#include "BugzillaJSONStructs.h" #include "GameFramework/Pawn.h" #include "BugPlacerPawn.generated.h" diff --git a/Source/Unrealzilla/Public/BugSubmissionForm.h b/Source/Unrealzilla/Public/BugSubmissionForm.h index e10adbb..f054a1a 100644 --- a/Source/Unrealzilla/Public/BugSubmissionForm.h +++ b/Source/Unrealzilla/Public/BugSubmissionForm.h @@ -81,7 +81,7 @@ private: UFUNCTION(BlueprintCallable) void ShowProcessingOverlayLoading(); UFUNCTION(BlueprintCallable) - void ShowProcessingOverlayMessage(const FString Message, const bool bExitAfterConfirm = false); + void ShowProcessingOverlayMessage(const struct FUnrealzillaErrorData &Error); UFUNCTION(BlueprintCallable) void HideProcessingOverlay(); diff --git a/Source/Unrealzilla/Public/ServerAPI.h b/Source/Unrealzilla/Public/ServerAPI.h index a4fb779..acb10c5 100644 --- a/Source/Unrealzilla/Public/ServerAPI.h +++ b/Source/Unrealzilla/Public/ServerAPI.h @@ -21,6 +21,8 @@ class UNREALZILLA_API UServerAPI : public UObject GENERATED_BODY() public: + virtual void Initialize() {} + virtual void ReturnListOfBugs(); virtual void PrepareForm(); 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 FBugzillaJSONFieldResponse &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 FormatQueryString(const TMap &QueryData); diff --git a/Source/Unrealzilla/Public/ServerBugzillaAPI.h b/Source/Unrealzilla/Public/ServerBugzillaAPI.h index d881706..e2279b6 100644 --- a/Source/Unrealzilla/Public/ServerBugzillaAPI.h +++ b/Source/Unrealzilla/Public/ServerBugzillaAPI.h @@ -16,7 +16,7 @@ class UNREALZILLA_API UServerBugzillaAPI : public UServerAPI GENERATED_BODY() public: - UServerBugzillaAPI(); + virtual void Initialize() override; virtual void ReturnListOfBugs() override; virtual void PrepareForm() override; diff --git a/Source/Unrealzilla/Public/ServerJiraAPI.h b/Source/Unrealzilla/Public/ServerJiraAPI.h index b182552..770ba3a 100644 --- a/Source/Unrealzilla/Public/ServerJiraAPI.h +++ b/Source/Unrealzilla/Public/ServerJiraAPI.h @@ -16,20 +16,39 @@ class UNREALZILLA_API UServerJiraAPI : public UServerAPI GENERATED_BODY() public: - UServerJiraAPI(); + virtual void Initialize() override; virtual void ReturnListOfBugs() override; virtual void PrepareForm() override; virtual void SendFormData(const FUnrealzillaPostData &PostData) override; 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 ServerPOSTResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); void ServerPOSTUpdateMarkerResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); - void ServerProductInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); - void ServerSeverityInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); - void ServerPlatformInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); - void ServerOSInfoResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void ServerFieldOptionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); + void ServerVersionsResponse(FHttpRequestPtr Request, FHttpResponsePtr Response, bool Success); 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; }; diff --git a/Source/Unrealzilla/Public/UnrealzillaGlobalSettings.h b/Source/Unrealzilla/Public/UnrealzillaGlobalSettings.h index 129a5c0..97914d9 100644 --- a/Source/Unrealzilla/Public/UnrealzillaGlobalSettings.h +++ b/Source/Unrealzilla/Public/UnrealzillaGlobalSettings.h @@ -36,12 +36,12 @@ public: UPROPERTY(Config, BlueprintReadOnly, EditDefaultsOnly, Category="Bug Placement", meta=(DisplayName="Arbitrary Placement Distance")) 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. UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting") 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. UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Bugzilla", meta=(DisplayName="Server")) FString BugzillaSubmissionServer; @@ -64,13 +64,43 @@ public: // The name of the project for which bugs will be posted. UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Project Name")) FString JiraProjectName; - // The username to use when posting bugs. - UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Username")) + // The username to use when posting bugs. Should be the user's full email address. + UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="Email")) 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. UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Reporting|Jira", meta=(DisplayName="API Key")) 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. UPROPERTY(Config, EditDefaultsOnly, BlueprintReadOnly, Category="Markers|Unresolved")