1329 lines
38 KiB
C++
1329 lines
38 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SBuildSelection.h"
|
|
|
|
#include "DesktopPlatformModule.h"
|
|
#include "Internationalization/FastDecimalFormat.h"
|
|
#include "Math/BasicMathExpressionEvaluator.h"
|
|
#include "Math/UnitConversion.h"
|
|
#include "Misc/App.h"
|
|
#include "Misc/ExpressionParser.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/UProjectInfo.h"
|
|
#include "SMultiSelectComboBox.h"
|
|
#include "Styling/StyleColors.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SCheckBox.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
#include "Widgets/Input/SHyperlink.h"
|
|
#include "Widgets/Layout/SGridPanel.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Widgets/Views/SHeaderRow.h"
|
|
#include "Widgets/Views/SListView.h"
|
|
#include "ZenServiceInstanceManager.h"
|
|
#include <atomic>
|
|
|
|
#define LOCTEXT_NAMESPACE "StorageServerBuild"
|
|
|
|
namespace UE::BuildSelection::Internal
|
|
{
|
|
namespace FBuildGroupIds
|
|
{
|
|
const FName ColName = TEXT("Name");
|
|
const FName ColCommit = TEXT("Commit");
|
|
const FName ColSuffix = TEXT("Suffix");
|
|
const FName ColCategory = TEXT("Category");
|
|
const FName ColCreated = TEXT("Created");
|
|
}
|
|
|
|
struct FNamespacePlatformBucketTuple
|
|
{
|
|
FString Namespace;
|
|
FString Platform;
|
|
FString Bucket;
|
|
};
|
|
|
|
struct FBuildState
|
|
{
|
|
FString Namespace;
|
|
FString Platform;
|
|
TArray<UE::Zen::Build::FBuildServiceInstance::FBuildRecord> Results;
|
|
};
|
|
|
|
struct FListBuildsState
|
|
{
|
|
std::atomic<uint32> PendingQueries;
|
|
TArray<FBuildState> QueryState;
|
|
};
|
|
}
|
|
|
|
void SBuildSelection::Construct(const FArguments& InArgs)
|
|
{
|
|
ZenServiceInstance = InArgs._ZenServiceInstance;
|
|
BuildServiceInstance = InArgs._BuildServiceInstance;
|
|
OnBuildTransferStarted = InArgs._OnBuildTransferStarted;
|
|
|
|
if (TSharedPtr<UE::Zen::Build::FBuildServiceInstance> ServiceInstance = BuildServiceInstance.Get())
|
|
{
|
|
ServiceInstance->OnRefreshNamespacesAndBucketsComplete().AddSP(this, &SBuildSelection::RebuildLists);
|
|
}
|
|
|
|
this->ChildSlot
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Fill)
|
|
.Padding(0, 0, 0, 0)
|
|
.Expose(GridSlot)
|
|
[
|
|
GetGridPanel()
|
|
]
|
|
];
|
|
}
|
|
|
|
SBuildSelection::EBuildType SBuildSelection::GetSelectedBuildType() const
|
|
{
|
|
if (!SelectedBuildType)
|
|
{
|
|
return EBuildType::Unknown;
|
|
}
|
|
else if (SelectedBuildType->Contains(TEXT("oplog")))
|
|
{
|
|
return EBuildType::Oplog;
|
|
}
|
|
else if (SelectedBuildType->Contains(TEXT("stagedbuild")) || SelectedBuildType->Contains(TEXT("staged-build")))
|
|
{
|
|
return EBuildType::StagedBuild;
|
|
}
|
|
else if (SelectedBuildType->Contains(TEXT("packagedbuild")) || SelectedBuildType->Contains(TEXT("packaged-build")))
|
|
{
|
|
return EBuildType::PackagedBuild;
|
|
}
|
|
else if (SelectedBuildType->Contains(TEXT("ugs-pcb")))
|
|
{
|
|
return EBuildType::EditorPreCompiledBinary;
|
|
}
|
|
else if (SelectedBuildType->Contains(TEXT("installedbuild")) || SelectedBuildType->Contains(TEXT("installed-build")))
|
|
{
|
|
return EBuildType::EditorInstalledBuild;
|
|
}
|
|
return EBuildType::Unknown;
|
|
}
|
|
|
|
void SBuildSelection::RebuildLists()
|
|
{
|
|
using namespace UE::Zen::Build;
|
|
using namespace UE::BuildSelection::Internal;
|
|
|
|
BuildListRefreshesInProgress.fetch_add(1);
|
|
BucketsToNamespaces.Empty();
|
|
StreamList.Empty();
|
|
ProjectList.Empty();
|
|
BuildTypeList.Empty();
|
|
PlatformList.Empty();
|
|
|
|
if (TSharedPtr<FBuildServiceInstance> ServiceInstance = BuildServiceInstance.Get())
|
|
{
|
|
TArray<FString> Namespaces;
|
|
TArray<FString> Projects;
|
|
TArray<FString> Streams;
|
|
TArray<FString> BuildTypes;
|
|
TArray<FString> Platforms;
|
|
TMultiMap<FString, FString> NamespacesAndBuckets = ServiceInstance->GetNamespacesAndBuckets();
|
|
|
|
auto StringToSegmentViews = [](const FString& Str, TArray<FStringView>& OutViews)
|
|
{
|
|
FStringView WorkingStringView(Str);
|
|
int32 CurrentIndex = 0;
|
|
while (WorkingStringView.FindChar(TCHAR('.'), CurrentIndex))
|
|
{
|
|
if (CurrentIndex != 0)
|
|
{
|
|
OutViews.Add(WorkingStringView.Left(CurrentIndex));
|
|
}
|
|
WorkingStringView.RightChopInline(CurrentIndex+1);
|
|
}
|
|
if (!WorkingStringView.IsEmpty())
|
|
{
|
|
OutViews.Add(WorkingStringView);
|
|
}
|
|
};
|
|
|
|
NamespacesAndBuckets.GetKeys(Namespaces);
|
|
|
|
auto ConvertToSharedPtrs = [] (TArray<FString>& Strings, TArray<TSharedPtr<FString>>& SharedStrings)
|
|
{
|
|
Strings.StableSort();
|
|
for (FString& String : Strings)
|
|
{
|
|
SharedStrings.Add(MakeShared<FString>(MoveTemp(String)));
|
|
}
|
|
};
|
|
|
|
auto ConformSelection = [](TSharedPtr<FString>& SelectedItem, const TArray<TSharedPtr<FString>>& SelectionList)
|
|
{
|
|
if (!SelectedItem)
|
|
{
|
|
SelectedItem = SelectionList.IsEmpty() ? nullptr : SelectionList[0];
|
|
return;
|
|
}
|
|
|
|
const TSharedPtr<FString>* FoundSelectionListItem = SelectionList.FindByPredicate([&SelectedItem](const TSharedPtr<FString>& Item)
|
|
{
|
|
return *Item == *SelectedItem;
|
|
});
|
|
|
|
SelectedItem = FoundSelectionListItem ? *FoundSelectionListItem : SelectionList.IsEmpty() ? nullptr : SelectionList[0];
|
|
};
|
|
|
|
const uint32 SegmentIndexProject = 0;
|
|
const uint32 SegmentIndexBuildType = 1;
|
|
const uint32 SegmentIndexStream = 2;
|
|
const uint32 SegmentIndexPlatform = 3;
|
|
const uint32 SegmentIndexNum = 4;
|
|
|
|
// Stream list generation and selection conforming
|
|
TMultiMap<FStringView, TArray<FStringView>> NamespacesToBucketSegmentViews;
|
|
for (const TPair<FString, FString>& NamespaceAndBucket : NamespacesAndBuckets)
|
|
{
|
|
BucketsToNamespaces.AddUnique(NamespaceAndBucket.Value, NamespaceAndBucket.Key);
|
|
TArray<FStringView>& BucketSegmentViews = NamespacesToBucketSegmentViews.Add(NamespaceAndBucket.Key);
|
|
StringToSegmentViews(NamespaceAndBucket.Value, BucketSegmentViews);
|
|
|
|
if (BucketSegmentViews.Num() == SegmentIndexNum)
|
|
{
|
|
Streams.AddUnique(FString(BucketSegmentViews[SegmentIndexStream]));
|
|
}
|
|
}
|
|
ConvertToSharedPtrs(Streams, StreamList);
|
|
ConformSelection(SelectedStream, StreamList);
|
|
|
|
// Project list generation and selection conforming
|
|
for (const TPair<FStringView, TArray<FStringView>>& NamespaceToBucketSegmentViews : NamespacesToBucketSegmentViews)
|
|
{
|
|
const TArray<FStringView>& BucketSegmentViews = NamespaceToBucketSegmentViews.Value;
|
|
if (BucketSegmentViews.Num() == SegmentIndexNum)
|
|
{
|
|
if (BucketSegmentViews[SegmentIndexStream] != (SelectedStream ? *SelectedStream : Streams[0]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Projects.AddUnique(FString(BucketSegmentViews[SegmentIndexProject]));
|
|
}
|
|
}
|
|
ConvertToSharedPtrs(Projects, ProjectList);
|
|
ConformSelection(SelectedProject, ProjectList);
|
|
|
|
// BuildType list generation and selection conforming
|
|
for (const TPair<FStringView, TArray<FStringView>>& NamespaceToBucketSegmentViews : NamespacesToBucketSegmentViews)
|
|
{
|
|
const TArray<FStringView>& BucketSegmentViews = NamespaceToBucketSegmentViews.Value;
|
|
if (BucketSegmentViews.Num() == SegmentIndexNum)
|
|
{
|
|
if (BucketSegmentViews[SegmentIndexStream] != (SelectedStream ? *SelectedStream : Streams[0]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BucketSegmentViews[SegmentIndexProject] != (SelectedProject ? *SelectedProject : Projects[0]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
BuildTypes.AddUnique(FString(BucketSegmentViews[SegmentIndexBuildType]));
|
|
}
|
|
}
|
|
ConvertToSharedPtrs(BuildTypes, BuildTypeList);
|
|
ConformSelection(SelectedBuildType, BuildTypeList);
|
|
|
|
// Platform list generation
|
|
for (const TPair<FStringView, TArray<FStringView>>& NamespaceToBucketSegmentViews : NamespacesToBucketSegmentViews)
|
|
{
|
|
const TArray<FStringView>& BucketSegmentViews = NamespaceToBucketSegmentViews.Value;
|
|
if (BucketSegmentViews.Num() == SegmentIndexNum)
|
|
{
|
|
if (BucketSegmentViews[SegmentIndexStream] != (SelectedStream ? *SelectedStream : Streams[0]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BucketSegmentViews[SegmentIndexProject] != (SelectedProject ? *SelectedProject : Projects[0]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (BucketSegmentViews[SegmentIndexBuildType] != (SelectedBuildType ? *SelectedBuildType : BuildTypes[0]))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Platforms.AddUnique(FString(BucketSegmentViews[SegmentIndexPlatform]));
|
|
}
|
|
}
|
|
ConvertToSharedPtrs(Platforms, PlatformList);
|
|
}
|
|
|
|
ExecuteOnGameThread(UE_SOURCE_LOCATION,
|
|
[this]
|
|
{
|
|
StreamWidget->RefreshOptions();
|
|
StreamWidget->SetSelectedItem(SelectedStream);
|
|
ProjectWidget->RefreshOptions();
|
|
ProjectWidget->SetSelectedItem(SelectedProject);
|
|
BuildTypeWidget->RefreshOptions();
|
|
BuildTypeWidget->SetSelectedItem(SelectedBuildType);
|
|
RegenerateActivePlatformFilters();
|
|
});
|
|
|
|
if (TSharedPtr<FBuildServiceInstance> ServiceInstance = BuildServiceInstance.Get())
|
|
{
|
|
TArray<FNamespacePlatformBucketTuple> NamespacePlatformBucketTuples;
|
|
for (TSharedPtr<FString> Platform : PlatformList)
|
|
{
|
|
FString Bucket = FString::Printf(TEXT("%s.%s.%s.%s"), **SelectedProject, **SelectedBuildType, **SelectedStream, **Platform);
|
|
TArray<FString> NamespacesForBucket;
|
|
BucketsToNamespaces.MultiFind(Bucket, NamespacesForBucket);
|
|
for (FString& Namespace : NamespacesForBucket)
|
|
{
|
|
NamespacePlatformBucketTuples.Emplace(MoveTemp(Namespace), *Platform, Bucket);
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FListBuildsState> PendingQueryState = MakeShared<FListBuildsState>();
|
|
PendingQueryState->PendingQueries = NamespacePlatformBucketTuples.Num();
|
|
PendingQueryState->QueryState.SetNum(NamespacePlatformBucketTuples.Num());
|
|
uint32 QueryIndex = 0;
|
|
|
|
++BuildRefreshGeneration;
|
|
BuildGroups.Empty();
|
|
|
|
for (const FNamespacePlatformBucketTuple& NamespacePlatformBucket : NamespacePlatformBucketTuples)
|
|
{
|
|
ServiceInstance->ListBuilds(NamespacePlatformBucket.Namespace, NamespacePlatformBucket.Bucket,
|
|
[this, QueryIndex, Namespace = NamespacePlatformBucket.Namespace, Platform = NamespacePlatformBucket.Platform,
|
|
ExpectedBuildRefreshGeneration = BuildRefreshGeneration.load(), PendingQueryState]
|
|
(TArray<FBuildServiceInstance::FBuildRecord>&& Results) mutable
|
|
{
|
|
FBuildState& NewBuildState = PendingQueryState->QueryState[QueryIndex];
|
|
NewBuildState.Namespace = MoveTemp(Namespace);
|
|
NewBuildState.Platform = MoveTemp(Platform);
|
|
NewBuildState.Results = MoveTemp(Results);
|
|
|
|
if (--PendingQueryState->PendingQueries == 0)
|
|
{
|
|
// All queries complete
|
|
if (ExpectedBuildRefreshGeneration == BuildRefreshGeneration)
|
|
{
|
|
// Expected generation is the current generation
|
|
RegenerateBuildGroups(*PendingQueryState);
|
|
|
|
ExecuteOnGameThread(UE_SOURCE_LOCATION,
|
|
[this]
|
|
{
|
|
BuildListView->RequestListRefresh();
|
|
BuildListRefreshesInProgress.fetch_sub(1);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// Expected generation is not the current generation
|
|
ExecuteOnGameThread(UE_SOURCE_LOCATION,
|
|
[this]
|
|
{
|
|
BuildListRefreshesInProgress.fetch_sub(1);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
++QueryIndex;
|
|
}
|
|
|
|
if (NamespacePlatformBucketTuples.IsEmpty())
|
|
{
|
|
BuildListRefreshesInProgress.fetch_sub(1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
BuildListRefreshesInProgress.fetch_sub(1);
|
|
}
|
|
}
|
|
|
|
void SBuildSelection::ReselectDestination(TSharedPtr<FBuildGroup> Item)
|
|
{
|
|
// TODO: Select the destination info (path & zen project id based on the selected item)
|
|
if (!SelectedBuildType)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EBuildType BuildType = GetSelectedBuildType();
|
|
FString ProjectFilename = FUProjectDictionary::GetDefault().GetProjectPathForGame(**SelectedProject);
|
|
if (ProjectFilename.IsEmpty())
|
|
{
|
|
if (BuildType != EBuildType::Oplog)
|
|
{
|
|
DestinationFolderPath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("DownloadedBuilds"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
switch (BuildType)
|
|
{
|
|
case EBuildType::Oplog:
|
|
DestinationZenProjectId = FApp::GetZenStoreProjectIdForProject(ProjectFilename);
|
|
break;
|
|
case EBuildType::StagedBuild:
|
|
DestinationFolderPath = FPaths::Combine(FPaths::GetPath(ProjectFilename), TEXT("Saved"), TEXT("StagedBuilds"));
|
|
break;
|
|
case EBuildType::PackagedBuild:
|
|
DestinationFolderPath = FPaths::Combine(FPaths::GetPath(ProjectFilename), TEXT("Saved"), TEXT("Packages"));
|
|
break;
|
|
default:
|
|
DestinationFolderPath = FPaths::Combine(FPaths::GetPath(ProjectFilename), TEXT("Saved"), TEXT("DownloadedBuilds"));
|
|
break;
|
|
}
|
|
}
|
|
|
|
void SBuildSelection::RegenerateBuildGroups(UE::BuildSelection::Internal::FListBuildsState& ListBuildsState)
|
|
{
|
|
using namespace UE::Zen::Build;
|
|
using namespace UE::BuildSelection::Internal;
|
|
|
|
BuildGroups.Empty();
|
|
|
|
auto MakeGroupKey = [](const FString& Namespace, const FString& CommitIdentifier, const FBuildServiceInstance::FBuildRecord& BuildRecord)
|
|
{
|
|
if (!CommitIdentifier.IsEmpty())
|
|
{
|
|
return FString(WriteToString<64>(Namespace, ".", CommitIdentifier));
|
|
}
|
|
if (FCbFieldView NameField = BuildRecord.Metadata["name"]; NameField.HasValue() && !NameField.HasError())
|
|
{
|
|
return FString(WriteToString<64>(Namespace, ".", NameField.AsString()));
|
|
}
|
|
return FString(WriteToString<64>(BuildRecord.BuildId));
|
|
};
|
|
|
|
TMap<FString, TSharedPtr<FBuildGroup>> KeyedGroups;
|
|
for (FBuildState& BuildState : ListBuildsState.QueryState)
|
|
{
|
|
for (FBuildServiceInstance::FBuildRecord& BuildRecord : BuildState.Results)
|
|
{
|
|
FString CommitIdentifier;
|
|
if (FCbFieldView ChangelistField = BuildRecord.Metadata["changelist"]; ChangelistField.HasValue() && !ChangelistField.HasError())
|
|
{
|
|
if (ChangelistField.IsString())
|
|
{
|
|
CommitIdentifier = FUTF8ToTCHAR(ChangelistField.AsString());
|
|
}
|
|
else if (ChangelistField.IsInteger())
|
|
{
|
|
CommitIdentifier = *WriteToString<64>(ChangelistField.AsUInt64());
|
|
}
|
|
else if (ChangelistField.IsFloat())
|
|
{
|
|
CommitIdentifier = *WriteToString<64>((uint64)ChangelistField.AsDouble());
|
|
}
|
|
}
|
|
else if (FCbFieldView CommitField = BuildRecord.Metadata["commit"]; CommitField.HasValue() && !CommitField.HasError())
|
|
{
|
|
if (CommitField.IsString())
|
|
{
|
|
CommitIdentifier = FUTF8ToTCHAR(CommitField.AsString());
|
|
}
|
|
else if (CommitField.IsInteger())
|
|
{
|
|
CommitIdentifier = *WriteToString<64>(CommitField.AsUInt64());
|
|
}
|
|
else if (CommitField.IsFloat())
|
|
{
|
|
CommitIdentifier = *WriteToString<64>((uint64)CommitField.AsDouble());
|
|
}
|
|
}
|
|
FString GroupKey = MakeGroupKey(BuildState.Namespace, CommitIdentifier, BuildRecord);
|
|
TSharedPtr<FBuildGroup>& BuildGroup = KeyedGroups.FindOrAdd(GroupKey);
|
|
if (!BuildGroup)
|
|
{
|
|
BuildGroup = MakeShared<FBuildGroup>();
|
|
}
|
|
|
|
if (BuildGroup->DisplayName.IsEmpty())
|
|
{
|
|
FString Category;
|
|
if (FCbFieldView TemplateIdField = BuildRecord.Metadata["hordeTemplateId"]; TemplateIdField.HasValue() && !TemplateIdField.HasError())
|
|
{
|
|
Category = FUTF8ToTCHAR(TemplateIdField.AsString());
|
|
}
|
|
|
|
FDateTime CreatedAt;
|
|
if (FCbFieldView CreatedAtField = BuildRecord.Metadata["createdAt"]; CreatedAtField.HasValue() && !CreatedAtField.HasError())
|
|
{
|
|
if (CreatedAtField.IsString())
|
|
{
|
|
FDateTime::ParseIso8601(FUTF8ToTCHAR(CreatedAtField.AsString()).Get(), CreatedAt);
|
|
}
|
|
else if (CreatedAtField.IsDateTime())
|
|
{
|
|
CreatedAt = CreatedAtField.AsDateTime();
|
|
}
|
|
}
|
|
|
|
FString ItemName;
|
|
if (FCbFieldView NameView = BuildRecord.Metadata["name"]; NameView.HasValue() && !NameView.HasError())
|
|
{
|
|
// TODO: This name manipulation needs to be removed when the metadata is more consistent.
|
|
ItemName = FUTF8ToTCHAR(NameView.AsString());
|
|
int32 CLStartIndex = ItemName.Find(TEXT("-CL"));
|
|
if (CLStartIndex != INDEX_NONE)
|
|
{
|
|
int32 TruncationIndex = ItemName.Find(TEXT("-"), ESearchCase::IgnoreCase, ESearchDir::FromStart, CLStartIndex + 4);
|
|
if (TruncationIndex != INDEX_NONE)
|
|
{
|
|
ItemName.LeftInline(TruncationIndex);
|
|
}
|
|
TruncationIndex = ItemName.Find(TEXT("."), ESearchCase::IgnoreCase, ESearchDir::FromStart, CLStartIndex + 4);
|
|
if (TruncationIndex != INDEX_NONE)
|
|
{
|
|
ItemName.LeftInline(TruncationIndex);
|
|
}
|
|
}
|
|
|
|
if (!Category.IsEmpty() && ItemName.StartsWith(Category))
|
|
{
|
|
ItemName.RightChopInline(Category.Len());
|
|
}
|
|
bool bCharRemoved = false;
|
|
do
|
|
{
|
|
ItemName.TrimCharInline(TCHAR('.'), &bCharRemoved);
|
|
}
|
|
while(bCharRemoved);
|
|
do
|
|
{
|
|
ItemName.TrimCharInline(TCHAR('+'), &bCharRemoved);
|
|
}
|
|
while(bCharRemoved);
|
|
|
|
ItemName.ReplaceCharInline(TCHAR('+'), TCHAR('-'));
|
|
}
|
|
else
|
|
{
|
|
ItemName = *WriteToString<64>(BuildRecord.BuildId);
|
|
}
|
|
|
|
BuildGroup->Namespace = BuildState.Namespace;
|
|
BuildGroup->DisplayName = ItemName;
|
|
BuildGroup->CommitIdentifier = CommitIdentifier;
|
|
BuildGroup->Category = Category;
|
|
BuildGroup->CreatedAt = CreatedAt;
|
|
}
|
|
|
|
BuildGroup->PerPlatformBuilds.FindOrAdd(BuildState.Platform, MoveTemp(BuildRecord));
|
|
}
|
|
}
|
|
|
|
KeyedGroups.GenerateValueArray(BuildGroups);
|
|
}
|
|
|
|
void SBuildSelection::RegenerateActivePlatformFilters()
|
|
{
|
|
ActivePlatformFilters.Empty();
|
|
for (TSharedPtr<FString> Platform : PlatformList)
|
|
{
|
|
if (Platform && RequiredPlatformsWidget)
|
|
{
|
|
if (RequiredPlatformsWidget->IsChecked(*Platform))
|
|
{
|
|
ActivePlatformFilters.Add(*Platform);
|
|
SelectedGroupSelectedPlatforms.AddUnique(*Platform);
|
|
}
|
|
else
|
|
{
|
|
SelectedGroupSelectedPlatforms.Remove(*Platform);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SBuildSelection::ValidateBuildGroupSelection()
|
|
{
|
|
BuildListView->UpdateSelectionSet();
|
|
TArray<FBuildSelectionBuildGroupPtr> SelectedItems = BuildListView->GetSelectedItems();
|
|
if (SelectedItems.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
for (const FBuildSelectionBuildGroupPtr& SelectedItem : SelectedItems)
|
|
{
|
|
if (!BuildGroupIsSelectableOrNavigable(SelectedItem))
|
|
{
|
|
BuildListView->SetItemSelection(SelectedItem, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SBuildSelection::OnGenerateTextBlockFromString(TSharedPtr<FString> Item)
|
|
{
|
|
return SNew(STextBlock)
|
|
.Text(FText::FromString(*Item));
|
|
}
|
|
|
|
bool SBuildSelection::BuildGroupIsSelectableOrNavigable(FBuildSelectionBuildGroupPtr InItem) const
|
|
{
|
|
if (!InItem)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const FString& ActivePlatformFilter : ActivePlatformFilters)
|
|
{
|
|
if (!InItem->PerPlatformBuilds.Contains(ActivePlatformFilter))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
TSharedRef<ITableRow> SBuildSelection::GenerateBuildGroupRow(FBuildSelectionBuildGroupPtr InItem, const TSharedRef<STableViewBase>& InOwningTable)
|
|
{
|
|
return SNew(SBuildGroupTableRow, InOwningTable, InItem)
|
|
.Visibility_Lambda([this, InItem]()
|
|
{
|
|
for (const FString& ActivePlatformFilter : ActivePlatformFilters)
|
|
{
|
|
if (!InItem->PerPlatformBuilds.Contains(ActivePlatformFilter))
|
|
{
|
|
return EVisibility::Collapsed;
|
|
}
|
|
}
|
|
return EVisibility::Visible;
|
|
});
|
|
}
|
|
|
|
void SBuildSelection::BuildGroupSelectionChanged(FBuildSelectionBuildGroupPtr Item, ESelectInfo::Type SelectInfo)
|
|
{
|
|
using namespace UE::Zen::Build;
|
|
|
|
if (!SelectedGroupPlatformGrid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SelectedGroupPlatformGrid->ClearChildren();
|
|
|
|
if (!Item)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ReselectDestination(Item);
|
|
|
|
int32 Row = 0;
|
|
int32 Column = 0;
|
|
|
|
for (const TSharedPtr<FString>& Platform : PlatformList)
|
|
{
|
|
TSharedPtr<SCheckBox> AssociatedCheckbox;
|
|
SelectedGroupPlatformGrid->AddSlot(Column, Row)
|
|
.Padding(0,0,0,2)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.IsEnabled_Lambda([this, Item, Platform]
|
|
{
|
|
return Item->PerPlatformBuilds.Contains(*Platform);
|
|
})
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew(AssociatedCheckbox, SCheckBox)
|
|
.IsChecked_Lambda([this, Item, Platform]
|
|
{
|
|
bool bIsChecked = Item->PerPlatformBuilds.Contains(*Platform) && SelectedGroupSelectedPlatforms.Contains(*Platform);
|
|
return bIsChecked ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
})
|
|
.OnCheckStateChanged_Lambda([this, Platform](ECheckBoxState InNewState)
|
|
{
|
|
if (InNewState == ECheckBoxState::Checked)
|
|
{
|
|
SelectedGroupSelectedPlatforms.AddUnique(*Platform);
|
|
}
|
|
else if (InNewState == ECheckBoxState::Unchecked)
|
|
{
|
|
SelectedGroupSelectedPlatforms.Remove(*Platform);
|
|
}
|
|
})
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SButton)
|
|
.ButtonStyle(FAppStyle::Get(), "InvisibleButton")
|
|
.IsFocusable(false)
|
|
|
|
.OnClicked_Lambda([this, AssociatedCheckbox, Platform]()
|
|
{
|
|
ECheckBoxState NewState = ECheckBoxState::Checked;
|
|
if (AssociatedCheckbox->IsChecked())
|
|
{
|
|
NewState = ECheckBoxState::Unchecked;
|
|
}
|
|
AssociatedCheckbox->SetIsChecked(NewState);
|
|
if (NewState == ECheckBoxState::Checked)
|
|
{
|
|
SelectedGroupSelectedPlatforms.AddUnique(*Platform);
|
|
}
|
|
else if (NewState == ECheckBoxState::Unchecked)
|
|
{
|
|
SelectedGroupSelectedPlatforms.Remove(*Platform);
|
|
}
|
|
return FReply::Handled();
|
|
})
|
|
[
|
|
SNew(STextBlock)
|
|
.Justification(ETextJustify::Left)
|
|
.Text(FText::FromString(*Platform))
|
|
]
|
|
]
|
|
];
|
|
|
|
if (++Row > 2)
|
|
{
|
|
Column++;
|
|
Row = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SBuildSelection::OnOpenDestinationDirectoryClicked()
|
|
{
|
|
bool bDirectorySelected = false;
|
|
|
|
if (IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get())
|
|
{
|
|
const FString Title = LOCTEXT("BuildSelection_DestinationDirectoryBrowserTitle", "Choose destination directory").ToString();
|
|
|
|
bDirectorySelected = DesktopPlatform->OpenDirectoryDialog(
|
|
FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr),
|
|
Title,
|
|
DestinationFolderPath,
|
|
DestinationFolderPath);
|
|
}
|
|
}
|
|
|
|
TSharedRef<SWidget> SBuildSelection::GetBuildDestinationPanel()
|
|
{
|
|
return
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility_Lambda([this]()
|
|
{
|
|
TArray<FBuildSelectionBuildGroupPtr> SelectedItems = BuildListView->GetSelectedItems();
|
|
return (SelectedItems.Num() == 0) || GetSelectedBuildType() == EBuildType::Oplog ? EVisibility::Collapsed : EVisibility::Visible;
|
|
})
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.OverflowPolicy(ETextOverflowPolicy::MiddleEllipsis)
|
|
.MinDesiredWidth(200.0f)
|
|
.Text_Lambda([this]()
|
|
{
|
|
return FText::FromString(DestinationFolderPath);
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& Text)
|
|
{
|
|
DestinationFolderPath = Text.ToString();
|
|
})
|
|
.OnTextCommitted_Lambda([this](const FText& Text, const ETextCommit::Type CommitType)
|
|
{
|
|
DestinationFolderPath = Text.ToString();
|
|
})
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SNew(SButton)
|
|
.OnClicked_Lambda([this]()
|
|
{
|
|
OnOpenDestinationDirectoryClicked();
|
|
return FReply::Handled();
|
|
})
|
|
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::Get().GetBrush("Zen.BrowseContent"))
|
|
.ColorAndOpacity(FSlateColor::UseForeground())
|
|
]
|
|
]
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
.Visibility_Lambda([this]()
|
|
{
|
|
TArray<FBuildSelectionBuildGroupPtr> SelectedItems = BuildListView->GetSelectedItems();
|
|
return (SelectedItems.Num() == 0) || GetSelectedBuildType() != EBuildType::Oplog ? EVisibility::Collapsed : EVisibility::Visible;
|
|
})
|
|
+ SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SEditableTextBox)
|
|
.OverflowPolicy(ETextOverflowPolicy::MiddleEllipsis)
|
|
.MinDesiredWidth(200.0f)
|
|
.Text_Lambda([this]()
|
|
{
|
|
return FText::FromString(DestinationZenProjectId);
|
|
})
|
|
.OnTextChanged_Lambda([this](const FText& Text)
|
|
{
|
|
DestinationZenProjectId = Text.ToString();
|
|
})
|
|
.OnTextCommitted_Lambda([this](const FText& Text, const ETextCommit::Type CommitType)
|
|
{
|
|
DestinationZenProjectId = Text.ToString();
|
|
})
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SWidget> SBuildSelection::GetGridPanel()
|
|
{
|
|
using namespace UE::BuildSelection::Internal;
|
|
|
|
TSharedRef<SVerticalBox> Panel =
|
|
SNew(SVerticalBox)
|
|
.IsEnabled_Lambda([this]
|
|
{
|
|
if (TSharedPtr<UE::Zen::Build::FBuildServiceInstance> ServiceInstance = BuildServiceInstance.Get())
|
|
{
|
|
return ServiceInstance->GetConnectionState() == UE::Zen::Build::FBuildServiceInstance::EConnectionState::ConnectionSucceeded && !ServiceInstance->GetNamespacesAndBuckets().IsEmpty();
|
|
}
|
|
return false;
|
|
});
|
|
|
|
const float MinDesiredWidth = 50.0f;
|
|
|
|
const float RowMargin = 2.0f;
|
|
const float ColumnMargin = 10.0f;
|
|
const FSlateColor TitleColor = FStyleColors::AccentWhite;
|
|
const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 10);
|
|
|
|
Panel->AddSlot()
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
// Stream
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(TitleColor)
|
|
.Font(TitleFont)
|
|
.Text(LOCTEXT("BuildSelection_Stream", "Stream"))
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SAssignNew(StreamWidget, SComboBox<TSharedPtr<FString>>)
|
|
.OptionsSource(&StreamList)
|
|
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> Item, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if (Item.IsValid() && *Item != *SelectedStream)
|
|
{
|
|
SelectedStream = Item;
|
|
RebuildLists();
|
|
}
|
|
})
|
|
.OnGenerateWidget(this, &SBuildSelection::OnGenerateTextBlockFromString)
|
|
[
|
|
SNew(STextBlock)
|
|
.MinDesiredWidth(MinDesiredWidth)
|
|
.Text_Lambda([this]()
|
|
{
|
|
return FText::FromString(SelectedStream ? **SelectedStream : TEXT(""));
|
|
})
|
|
]
|
|
]
|
|
]
|
|
|
|
// Project
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(TitleColor)
|
|
.Font(TitleFont)
|
|
.Text(LOCTEXT("BuildSelection_Project", "Project"))
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SAssignNew(ProjectWidget, SComboBox<TSharedPtr<FString>>)
|
|
.OptionsSource(&ProjectList)
|
|
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> Item, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if (Item.IsValid() && *Item != *SelectedProject)
|
|
{
|
|
SelectedProject = Item;
|
|
RebuildLists();
|
|
}
|
|
})
|
|
.OnGenerateWidget(this, &SBuildSelection::OnGenerateTextBlockFromString)
|
|
[
|
|
SNew(STextBlock)
|
|
.MinDesiredWidth(MinDesiredWidth)
|
|
.Text_Lambda([this]()
|
|
{
|
|
return FText::FromString(SelectedProject ? **SelectedProject : TEXT(""));
|
|
})
|
|
]
|
|
]
|
|
]
|
|
|
|
// Build Type
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(TitleColor)
|
|
.Font(TitleFont)
|
|
.Text(LOCTEXT("BuildSelection_BuildType", "Build Type"))
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SAssignNew(BuildTypeWidget, SComboBox<TSharedPtr<FString>>)
|
|
.OptionsSource(&BuildTypeList)
|
|
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> Item, ESelectInfo::Type SelectInfo)
|
|
{
|
|
if (Item.IsValid() && *Item != *SelectedBuildType)
|
|
{
|
|
SelectedBuildType = Item;
|
|
RebuildLists();
|
|
}
|
|
})
|
|
.OnGenerateWidget(this, &SBuildSelection::OnGenerateTextBlockFromString)
|
|
[
|
|
SNew(STextBlock)
|
|
.MinDesiredWidth(MinDesiredWidth)
|
|
.Text_Lambda([this]()
|
|
{
|
|
return FText::FromString(SelectedBuildType ? **SelectedBuildType : TEXT(""));
|
|
})
|
|
]
|
|
]
|
|
]
|
|
|
|
// Required Platforms
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.FillWidth(1.0f)
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(SVerticalBox)
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(TitleColor)
|
|
.Font(TitleFont)
|
|
.Text(LOCTEXT("BuildSelection_RequiredPlatforms", "Required Platforms"))
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SAssignNew(RequiredPlatformsWidget, SMultiSelectComboBox)
|
|
.SelectValues(&PlatformList)
|
|
.OnCheckedValuesChanged_Lambda([this]()
|
|
{
|
|
RegenerateActivePlatformFilters();
|
|
ValidateBuildGroupSelection();
|
|
})
|
|
]
|
|
]
|
|
];
|
|
|
|
Panel->AddSlot()
|
|
.Padding(FMargin(ColumnMargin, 10, ColumnMargin, 0))
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(TitleColor)
|
|
.Font(TitleFont)
|
|
.Text(LOCTEXT("BuildSelection_BuildsLabel", "Builds"))
|
|
];
|
|
|
|
Panel->AddSlot()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Fill)
|
|
[
|
|
SAssignNew(BuildListView, SListView<FBuildSelectionBuildGroupPtr>)
|
|
.ListItemsSource(&BuildGroups)
|
|
.OnGenerateRow(this, &SBuildSelection::GenerateBuildGroupRow)
|
|
.OnSelectionChanged(this, &SBuildSelection::BuildGroupSelectionChanged)
|
|
.SelectionMode(ESelectionMode::Single)
|
|
.OnIsSelectableOrNavigable(this, &SBuildSelection::BuildGroupIsSelectableOrNavigable)
|
|
.IsEnabled_Lambda([this]
|
|
{
|
|
return !BuildListRefreshesInProgress;
|
|
})
|
|
.HeaderRow
|
|
(
|
|
SNew(SHeaderRow)
|
|
+ SHeaderRow::Column(FBuildGroupIds::ColName).DefaultLabel(LOCTEXT("BuildSelection_BuildGroupColName", "Name"))
|
|
.FillWidth(0.4f)
|
|
+ SHeaderRow::Column(FBuildGroupIds::ColCommit).DefaultLabel(LOCTEXT("BuildSelection_BuildGroupColCommit", "Commit"))
|
|
.DefaultTooltip(LOCTEXT("BuildSelection_BuildGroupColCommitTooltip", "Commit/Changelist for the build"))
|
|
.FillWidth(0.10f).HAlignCell(HAlign_Center).HAlignHeader(HAlign_Center).VAlignCell(VAlign_Center)
|
|
+ SHeaderRow::Column(FBuildGroupIds::ColSuffix).DefaultLabel(LOCTEXT("BuildSelection_BuildGroupColSuffix", "Suffix"))
|
|
.DefaultTooltip(LOCTEXT("BuildSelection_BuildGroupColSuffixTooltip", "Modifier on top of the commit/changelist for the build"))
|
|
.FillWidth(0.10f).HAlignCell(HAlign_Center).HAlignHeader(HAlign_Center).VAlignCell(VAlign_Center)
|
|
+ SHeaderRow::Column(FBuildGroupIds::ColCategory).DefaultLabel(LOCTEXT("BuildSelection_BuildGroupColCategory", "Category"))
|
|
.DefaultTooltip(LOCTEXT("BuildSelection_BuildGroupColCategoryTooltip", "Category for the build"))
|
|
.FillWidth(0.25f).HAlignCell(HAlign_Left).HAlignHeader(HAlign_Center).VAlignCell(VAlign_Center)
|
|
+ SHeaderRow::Column(FBuildGroupIds::ColCreated).DefaultLabel(LOCTEXT("BuildSelection_BuildGroupColCreated", "Created"))
|
|
.DefaultTooltip(LOCTEXT("BuildSelection_BuildGroupColCreatedTooltip", "When the build was created"))
|
|
.FillWidth(0.15f).HAlignCell(HAlign_Left).HAlignHeader(HAlign_Center).VAlignCell(VAlign_Center)
|
|
)
|
|
]
|
|
];
|
|
|
|
Panel->AddSlot()
|
|
.Padding(FMargin(ColumnMargin, 3, ColumnMargin, 0))
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Bottom)
|
|
[
|
|
SNew(STextBlock)
|
|
.Font(FAppStyle::Get().GetFontStyle("SmallFont"))
|
|
.Text_Lambda([this]()
|
|
{
|
|
if (BuildListRefreshesInProgress)
|
|
{
|
|
return LOCTEXT("BuildSelection_ResultLoading", "Loading...");
|
|
}
|
|
int32 VisibleItemCount = 0;
|
|
if (ActivePlatformFilters.IsEmpty())
|
|
{
|
|
VisibleItemCount = BuildGroups.Num();
|
|
}
|
|
else
|
|
{
|
|
for (FBuildSelectionBuildGroupPtr BuildGroup : BuildGroups)
|
|
{
|
|
bool bHasAllRequiredPlatforms = true;
|
|
for (const FString& ActivePlatformFilter : ActivePlatformFilters)
|
|
{
|
|
if (!BuildGroup->PerPlatformBuilds.Contains(ActivePlatformFilter))
|
|
{
|
|
bHasAllRequiredPlatforms = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bHasAllRequiredPlatforms)
|
|
{
|
|
VisibleItemCount++;
|
|
}
|
|
}
|
|
}
|
|
if (VisibleItemCount == 1)
|
|
{
|
|
return LOCTEXT("BuildSelection_ResultDescriptionMultiple", "1 item");
|
|
}
|
|
else
|
|
{
|
|
return FText::Format(LOCTEXT("BuildSelection_ResultDescriptionMultiple", "{0} items"), FText::AsNumber(VisibleItemCount));
|
|
}
|
|
})
|
|
];
|
|
|
|
Panel->AddSlot()
|
|
.Padding(FMargin(ColumnMargin, RowMargin))
|
|
.AutoHeight()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Bottom)
|
|
[
|
|
|
|
SNew(SHorizontalBox)
|
|
.Visibility_Lambda([this]()
|
|
{
|
|
return !!BuildListRefreshesInProgress || BuildListView->GetNumItemsSelected() == 0 ? EVisibility::Collapsed : EVisibility::Visible;
|
|
})
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Left)
|
|
.FillWidth(0.5f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
.AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(TitleColor)
|
|
.Font(TitleFont)
|
|
.Text(LOCTEXT("BuildSelection_AvailablePlatforms", "Available Platforms"))
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(SelectedGroupPlatformGrid, SGridPanel)
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.FillWidth(0.5f)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+SVerticalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
.AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity(TitleColor)
|
|
.Font(TitleFont)
|
|
.Text(LOCTEXT("BuildSelection_Destination", "Destination"))
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.HAlign(HAlign_Fill)
|
|
.VAlign(VAlign_Top)
|
|
[
|
|
GetBuildDestinationPanel()
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.HAlign(HAlign_Right)
|
|
.VAlign(VAlign_Bottom)
|
|
.Padding(FMargin(0, RowMargin))
|
|
[
|
|
SNew(SButton)
|
|
.Text(LOCTEXT("BuildSelection_Download", "Download"))
|
|
.ToolTipText(LOCTEXT("BuildSelection_DownloadTooltip", "Start a download of the selected build for the selected platforms"))
|
|
.ButtonStyle(FAppStyle::Get(), "Button")
|
|
.IsEnabled_Lambda([this]()
|
|
{
|
|
const bool bDestinationValid = GetSelectedBuildType() == EBuildType::Oplog ? !DestinationZenProjectId.IsEmpty() : !DestinationFolderPath.IsEmpty();
|
|
if (!bDestinationValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<FBuildSelectionBuildGroupPtr> SelectedItems = BuildListView->GetSelectedItems();
|
|
if (SelectedItems.Num() == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (const FString& PlatformForSelectedGroup : SelectedGroupSelectedPlatforms)
|
|
{
|
|
if (SelectedItems[0]->PerPlatformBuilds.Contains(PlatformForSelectedGroup))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
})
|
|
.OnClicked_Lambda([this]()
|
|
{
|
|
using namespace UE::Zen::Build;
|
|
|
|
if (TSharedPtr<FBuildServiceInstance> ServiceInstance = BuildServiceInstance.Get())
|
|
{
|
|
TArray<FBuildSelectionBuildGroupPtr> SelectedItems = BuildListView->GetSelectedItems();
|
|
if (SelectedItems.Num() == 0)
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
for (const FString& PlatformForSelectedGroup : SelectedGroupSelectedPlatforms)
|
|
{
|
|
if (FBuildServiceInstance::FBuildRecord* BuildRecord = SelectedItems[0]->PerPlatformBuilds.Find(PlatformForSelectedGroup))
|
|
{
|
|
FString Bucket = FString::Printf(TEXT("%s.%s.%s.%s"), **SelectedProject, **SelectedBuildType, **SelectedStream, *PlatformForSelectedGroup);
|
|
if (GetSelectedBuildType() == EBuildType::Oplog)
|
|
{
|
|
if (FCbFieldView CookPlatformField = BuildRecord->Metadata["cookPlatform"]; CookPlatformField.HasValue() && !CookPlatformField.HasError())
|
|
{
|
|
FString ProjectFilePath = FUProjectDictionary::GetDefault().GetProjectPathForGame(**SelectedProject);
|
|
FString DestinationOplogId = *WriteToString<64>(CookPlatformField.AsString());
|
|
FBuildServiceInstance::FBuildTransfer BuildTransfer =
|
|
ServiceInstance->StartOplogBuildTransfer(BuildRecord->BuildId, DestinationZenProjectId, DestinationOplogId, ProjectFilePath, SelectedItems[0]->Namespace, Bucket);
|
|
OnBuildTransferStarted.ExecuteIfBound(BuildTransfer, SelectedItems[0]->DisplayName, PlatformForSelectedGroup);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FString DestinationFolder = FPaths::Combine(DestinationFolderPath, PlatformForSelectedGroup);
|
|
FBuildServiceInstance::FBuildTransfer BuildTransfer =
|
|
ServiceInstance->StartBuildTransfer(BuildRecord->BuildId, DestinationFolder, SelectedItems[0]->Namespace, Bucket);
|
|
OnBuildTransferStarted.ExecuteIfBound(BuildTransfer, SelectedItems[0]->DisplayName, PlatformForSelectedGroup);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return FReply::Handled();
|
|
})
|
|
]
|
|
]
|
|
];
|
|
|
|
return Panel;
|
|
}
|
|
|
|
void
|
|
SBuildGroupTableRow::Construct(const FArguments& InArgs, const TSharedRef<STableViewBase>& InOwnerTableView, const FBuildSelectionBuildGroupPtr InBuildGroup)
|
|
{
|
|
BuildGroup = InBuildGroup;
|
|
|
|
SMultiColumnTableRow<FBuildSelectionBuildGroupPtr>::Construct(FSuperRowType::FArguments(), InOwnerTableView);
|
|
}
|
|
|
|
TSharedRef<SWidget>
|
|
SBuildGroupTableRow::GenerateWidgetForColumn(const FName& ColumnName)
|
|
{
|
|
using namespace UE::BuildSelection::Internal;
|
|
|
|
if (ColumnName == FBuildGroupIds::ColName)
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(STextBlock).Text(FText::FromString(BuildGroup->DisplayName))
|
|
];
|
|
}
|
|
else if (ColumnName == FBuildGroupIds::ColCommit)
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(STextBlock).Text(FText::FromString(BuildGroup->CommitIdentifier))
|
|
];
|
|
}
|
|
else if (ColumnName == FBuildGroupIds::ColSuffix)
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(STextBlock).Text(FText::FromString(BuildGroup->Suffix))
|
|
];
|
|
}
|
|
else if (ColumnName == FBuildGroupIds::ColCategory)
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(STextBlock).Text(FText::FromString(BuildGroup->Category))
|
|
];
|
|
}
|
|
else if (ColumnName == FBuildGroupIds::ColCreated)
|
|
{
|
|
if (BuildGroup->CreatedAt.GetTicks() != 0)
|
|
{
|
|
return SNew(SHorizontalBox)
|
|
+ SHorizontalBox::Slot()
|
|
[
|
|
SNew(STextBlock).Text(FText::AsDateTime(BuildGroup->CreatedAt, EDateTimeStyle::Short))
|
|
];
|
|
}
|
|
}
|
|
|
|
return SNullWidget::NullWidget;
|
|
}
|
|
|
|
const FSlateBrush*
|
|
SBuildGroupTableRow::GetBorder() const
|
|
{
|
|
return STableRow<FBuildSelectionBuildGroupPtr>::GetBorder();
|
|
}
|
|
|
|
FReply
|
|
SBuildGroupTableRow::OnBrowseClicked()
|
|
{
|
|
return FReply::Unhandled();
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|