Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/ReviewComments.cpp
2025-05-18 13:04:45 +08:00

402 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ReviewComments.h"
#include "UObject/Class.h"
#include "Serialization/JsonSerializer.h"
namespace
{
FString LowerCamelCase(const FString& In)
{
FString Result = In;
if (!Result.IsEmpty())
{
Result[0] = tolower(Result[0]);
}
return Result;
}
bool TryGetJsonObjectField(FReviewTopic* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
*OutVal = FReviewTopic::FromString(Value->AsString());
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const FReviewTopic& Val, const FString& Key)
{
JsonObject->SetStringField(Key, Val.ToString());
}
bool TryGetJsonObjectField(int32* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
*OutVal = (int32)Value->AsNumber();
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const int32& Val, const FString& Key)
{
JsonObject->SetNumberField(Key, Val);
}
bool TryGetJsonObjectField(FString* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
*OutVal = Value->AsString();
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const FString& Val, const FString& Key)
{
JsonObject->SetStringField(Key, Val);
}
bool TryGetJsonObjectField(FReviewCommentContext* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
// for some reason the API stores null-contexts as empty arrays.
if (Value->Type == EJson::Array)
{
checkSlow(Value->AsArray().IsEmpty());
return false;
}
*OutVal = FReviewCommentContext::FromJson(Value->AsObject());
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const FReviewCommentContext& Val, const FString& Key)
{
JsonObject->SetObjectField(Key, Val.ToJsonObject());
}
bool TryGetJsonObjectField(TSet<FString>* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
for (const TSharedPtr<FJsonValue>& Item : Value->AsArray())
{
OutVal->Add(Item->AsString());
}
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const TSet<FString>& Val, const FString& Key)
{
TArray<TSharedPtr<FJsonValue>> Array;
for (const FString& Element : Val)
{
Array.Add(MakeShared<FJsonValueString>(Element));
}
JsonObject->SetArrayField(Key, Array);
}
bool TryGetJsonObjectField(TArray<FString>* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
for (const TSharedPtr<FJsonValue>& Item : Value->AsArray())
{
OutVal->Add(Item->AsString());
}
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const TArray<FString>& Val, const FString& Key)
{
TArray<TSharedPtr<FJsonValue>> Array;
for (const FString& Element : Val)
{
Array.Add(MakeShared<FJsonValueString>(Element));
}
JsonObject->SetArrayField(Key, Array);
}
bool TryGetJsonObjectField(EReviewCommentTaskState* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
*OutVal = (EReviewCommentTaskState)StaticEnum<EReviewCommentTaskState>()->GetValueByNameString(Value->AsString());
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const EReviewCommentTaskState& Val, const FString& Key)
{
const FString EnumString = StaticEnum<EReviewCommentTaskState>()->GetValueAsString(Val);
int32 ChopIndex = INDEX_NONE;
if (EnumString.FindLastChar(':', ChopIndex))
{
JsonObject->SetStringField(Key, LowerCamelCase(EnumString.RightChop(ChopIndex + 1)));
}
}
bool TryGetJsonObjectField(FDateTime* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
if (const TSharedPtr<FJsonValue> &Value = JsonObject->TryGetField(Key))
{
if (Value->IsNull())
{
return false;
}
*OutVal = FDateTime::FromUnixTimestamp((int64)Value->AsNumber());
return true;
}
return false;
}
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const FDateTime& Val, const FString& Key)
{
JsonObject->SetNumberField(Key, Val.ToUnixTimestamp());
}
template<typename OptionalType>
bool TryGetJsonObjectField(TOptional<OptionalType>* OutVal, const TSharedPtr<FJsonObject>& JsonObject, const FString& Key)
{
OptionalType Val;
if (TryGetJsonObjectField(&Val, JsonObject, Key))
{
*OutVal = Val;
return true;
}
return false;
}
template<typename OptionalType>
void TrySetJsonObjectField(TSharedPtr<FJsonObject> JsonObject, const TOptional<OptionalType>& Val, const FString& Key)
{
if (Val.IsSet())
{
TrySetJsonObjectField(JsonObject, Val.GetValue(), Key);
}
}
#define TRY_SET_FROM_JSON(OwningObject, Key, JsonObject) TryGetJsonObjectField(&(OwningObject).Key, JsonObject, LowerCamelCase(#Key))
#define TRY_SET_FROM_JSON_NAMED(OwningObject, Key, JsonObject, Name) TryGetJsonObjectField(&(OwningObject).Key, JsonObject, LowerCamelCase(Name))
#define SET_JSON_FIELD(JsonObject, OwningObject, Key) TrySetJsonObjectField(JsonObject, (OwningObject).Key, LowerCamelCase(#Key))
#define SET_JSON_FIELD_NAMED(JsonObject, OwningObject, Key, Name) TrySetJsonObjectField(JsonObject, (OwningObject).Key, LowerCamelCase(Name))
}
FString FReviewCommentContext::ToJson() const
{
FString Result;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Result);
FJsonSerializer::Serialize(ToJsonObject().ToSharedRef(), Writer);
return Result;
}
TSharedPtr<FJsonObject> FReviewCommentContext::ToJsonObject() const
{
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
SET_JSON_FIELD(Result, *this, File);
SET_JSON_FIELD(Result, *this, Version);
SET_JSON_FIELD(Result, *this, Content);
SET_JSON_FIELD(Result, *this, LeftLine);
SET_JSON_FIELD(Result, *this, RightLine);
SET_JSON_FIELD_NAMED(Result, *this, ReplyTo, TEXT("comment"));
return Result;
}
FReviewCommentContext FReviewCommentContext::FromJson(const FString& Json)
{
const TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(Json);
TSharedPtr<FJsonValue> JsonValue;
if (FJsonSerializer::Deserialize(JsonReader, JsonValue))
{
const TSharedPtr< FJsonObject > JsonObject = JsonValue->AsObject();
return FromJson(JsonObject);
}
return {};
}
FReviewCommentContext FReviewCommentContext::FromJson(const TSharedPtr<FJsonObject>& JsonObject)
{
FReviewCommentContext Result = {};
if (!JsonObject.IsValid())
{
return Result;
}
TRY_SET_FROM_JSON(Result, File, JsonObject);
TRY_SET_FROM_JSON(Result, Version, JsonObject);
TRY_SET_FROM_JSON(Result, Content, JsonObject);
TRY_SET_FROM_JSON(Result, LeftLine, JsonObject);
TRY_SET_FROM_JSON(Result, RightLine, JsonObject);
TRY_SET_FROM_JSON_NAMED(Result, ReplyTo, JsonObject, TEXT("comment"));
return Result;
}
FString FReviewTopic::ToString() const
{
switch(TopicType)
{
case EReviewTopicType::Review:
return FString::Format(TEXT("reviews/{0}"), {ChangelistNum});
case EReviewTopicType::Change:
return FString::Format(TEXT("changes/{0}"), {ChangelistNum});
}
return {};
}
FReviewTopic FReviewTopic::FromString(const FString& ReviewTopic)
{
FReviewTopic Result;
FString TopicTypeString;
ReviewTopic.Split("/", &TopicTypeString, &Result.ChangelistNum);
if (TopicTypeString == TEXT("reviews"))
{
Result.TopicType = EReviewTopicType::Review;
}
else if (TopicTypeString == TEXT("changes"))
{
Result.TopicType = EReviewTopicType::Change;
}
else
{
Result.TopicType = EReviewTopicType::Unrecognised;
}
return Result;
}
FString FReviewComment::ToJson() const
{
FString Result;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Result);
FJsonSerializer::Serialize(ToJsonObject().ToSharedRef(), Writer);
return Result;
}
TSharedPtr<FJsonObject> FReviewComment::ToJsonObject() const
{
TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
SET_JSON_FIELD(Result, *this, Topic);
SET_JSON_FIELD(Result, *this, Body);
SET_JSON_FIELD(Result, *this, Context);
// don't include context if it doesn't have any fields
const TMap<FString, TSharedPtr<FJsonValue>>& ContextValues = Result->GetObjectField(TEXT("Context"))->Values;
if (ContextValues.IsEmpty())
{
Result->RemoveField(TEXT("Context"));
}
SET_JSON_FIELD(Result, *this, Likes);
SET_JSON_FIELD(Result, *this, ReadBy);
SET_JSON_FIELD(Result, *this, TaskState);
SET_JSON_FIELD(Result, *this, User);
SET_JSON_FIELD_NAMED(Result, *this, CommentID, TEXT("id"));
SET_JSON_FIELD_NAMED(Result, *this, CreatedTime, TEXT("time"));
SET_JSON_FIELD_NAMED(Result, *this, EditedTime, TEXT("edited"));
SET_JSON_FIELD_NAMED(Result, *this, UpdatedTime, TEXT("updated"));
if (bIsClosed)
{
TrySetJsonObjectField(Result, TArray<FString>{TEXT("closed")}, TEXT("flags"));
}
return Result;
}
FReviewComment FReviewComment::FromJson(const FString& Json)
{
const TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(Json);
TSharedPtr<FJsonValue> JsonValue;
if (FJsonSerializer::Deserialize(JsonReader, JsonValue))
{
const TSharedPtr< FJsonObject > JsonObject = JsonValue->AsObject();
return FromJson(JsonObject);
}
return {};
}
FReviewComment FReviewComment::FromJson(const TSharedPtr<FJsonObject>& JsonObject)
{
FReviewComment Result = {};
if (!JsonObject.IsValid())
{
return Result;
}
TRY_SET_FROM_JSON(Result, Topic, JsonObject);
TRY_SET_FROM_JSON(Result, Body, JsonObject);
TRY_SET_FROM_JSON(Result, Context, JsonObject);
TRY_SET_FROM_JSON(Result, Likes, JsonObject);
TRY_SET_FROM_JSON(Result, ReadBy, JsonObject);
TRY_SET_FROM_JSON(Result, TaskState, JsonObject);
TRY_SET_FROM_JSON(Result, User, JsonObject);
TRY_SET_FROM_JSON_NAMED(Result, CommentID, JsonObject, TEXT("id"));
TRY_SET_FROM_JSON_NAMED(Result, CreatedTime, JsonObject, TEXT("time"));
TRY_SET_FROM_JSON_NAMED(Result, EditedTime, JsonObject, TEXT("edited"));
TRY_SET_FROM_JSON_NAMED(Result, UpdatedTime, JsonObject, TEXT("updated"));
// set flags
Result.bIsClosed = false;
if (const TSharedPtr<FJsonValue> &FlagsField = JsonObject->TryGetField(TEXT("flags")))
{
if (!FlagsField->IsNull())
{
for (const TSharedPtr<FJsonValue>& Item : FlagsField->AsArray())
{
FString Flag = Item->AsString();
if (Flag == TEXT("closed"))
{
Result.bIsClosed = true;
}
else
{
// swarm documentation doesn't mention any flags except 'closed'. if this is hit, let jordan.hoffmann know
ensure(false);
}
}
}
}
return Result;
}