// Copyright Epic Games, Inc. All Rights Reserved. #include "SBuildActivity.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Internationalization/FastDecimalFormat.h" #include "Math/BasicMathExpressionEvaluator.h" #include "Math/UnitConversion.h" #include "Misc/ExpressionParser.h" #include "Styling/StyleColors.h" #include "Widgets/Images/SImage.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SHyperlink.h" #include "Widgets/Layout/SGridPanel.h" #include "Widgets/Notifications/SProgressBar.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Views/SListView.h" #include "ZenServiceInstanceManager.h" #define LOCTEXT_NAMESPACE "StorageServerBuild" namespace UE::BuildActivity::Internal { namespace FBuildActivityIds { const FName ColStatus = TEXT("Status"); const FName ColName = TEXT("Name"); const FName ColPlatform = TEXT("Platform"); const FName ColDescription = TEXT("Description"); } } void SBuildActivity::Construct(const FArguments& InArgs) { ZenServiceInstance = InArgs._ZenServiceInstance; BuildServiceInstance = InArgs._BuildServiceInstance; this->ChildSlot [ SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(0, 0, 0, 0) .Expose(GridSlot) [ GetGridPanel() ] ]; } TSharedRef SBuildActivity::GenerateBuildActivityRow(FBuildActivityPtr InItem, const TSharedRef& InOwningTable) { return SNew(SBuildActivityTableRow, InOwningTable, InItem, BuildServiceInstance.Get()); } TSharedRef SBuildActivity::GetGridPanel() { using namespace UE::BuildActivity::Internal; TSharedRef Panel = SNew(SVerticalBox) .Visibility_Lambda([this]() { return BuildActivities.IsEmpty() ? EVisibility::Collapsed : EVisibility::Visible; }); const float RowMargin = 0.0f; const float ColumnMargin = 10.0f; const FSlateColor TitleColor = FStyleColors::AccentWhite; const FSlateFontInfo TitleFont = FCoreStyle::GetDefaultFontStyle("Bold", 10); Panel->AddSlot() .AutoHeight() [ SNew(STextBlock) .Margin(FMargin(ColumnMargin, RowMargin)) .ColorAndOpacity(TitleColor) .Font(TitleFont) .Text(LOCTEXT("BuildActivity_Activity", "Activity")) ]; Panel->AddSlot() .AutoHeight() .Padding(FMargin(ColumnMargin, RowMargin)) [ SAssignNew(BuildActivityListView, SListView) .ListItemsSource(&BuildActivities) .OnGenerateRow(this, &SBuildActivity::GenerateBuildActivityRow) .OnMouseButtonDoubleClick(this, &SBuildActivity::OnItemDoubleClicked) .OnContextMenuOpening(this, &SBuildActivity::OnGetBuildActivityContextMenuContent) .HeaderRow ( SNew(SHeaderRow) .Visibility(EVisibility::Collapsed) + SHeaderRow::Column(FBuildActivityIds::ColStatus).DefaultLabel(LOCTEXT("BuildActivity_ActivityStatus", "Status")) .FillWidth(0.1f) + SHeaderRow::Column(FBuildActivityIds::ColName).DefaultLabel(LOCTEXT("BuildActivity_ActivityName", "Name")) .FillWidth(0.2f).HAlignCell(HAlign_Left).HAlignHeader(HAlign_Left).VAlignCell(VAlign_Center) + SHeaderRow::Column(FBuildActivityIds::ColPlatform).DefaultLabel(LOCTEXT("BuildActivity_ActivityPlatform", "Platform")) .FillWidth(0.1f).HAlignCell(HAlign_Left).HAlignHeader(HAlign_Left).VAlignCell(VAlign_Center) + SHeaderRow::Column(FBuildActivityIds::ColDescription).DefaultLabel(LOCTEXT("BuildActivity_ActivityDescription", "Description")) .FillWidth(0.6f).HAlignCell(HAlign_Fill).HAlignHeader(HAlign_Center).VAlignCell(VAlign_Center) ) ]; return Panel; } void SBuildActivity::OnItemDoubleClicked(TSharedPtr Item) { OpenDestinationForBuildActivity(Item); } TSharedPtr SBuildActivity::OnGetBuildActivityContextMenuContent() { TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.IsEmpty()) { return nullptr; } const bool bCloseAfterSelection = true; const bool bCloseSelfOnly = false; const bool bSearchable = false; const bool bRecursivelySearchable = false; FMenuBuilder MenuBuilder(bCloseAfterSelection, nullptr, TSharedPtr(), bCloseSelfOnly, &FCoreStyle::Get(), bSearchable, NAME_None, bRecursivelySearchable); MenuBuilder.AddMenuEntry( LOCTEXT("BuildActivity_OpenDestination", "Open destination"), LOCTEXT("BuildActivity_OpenDestinationTooltip", "Open the destination directory or page for the activity"), FSlateIcon(), FUIAction( FExecuteAction::CreateRaw(this, &SBuildActivity::OpenDestinationForBuildActivity) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( LOCTEXT("BuildActivity_ViewLog", "View log"), LOCTEXT("BuildActivity_ViewLogTooltip", "View log file for the activity"), FSlateIcon(), FUIAction( FExecuteAction::CreateRaw(this, &SBuildActivity::ViewLogForBuildActivity), FCanExecuteAction::CreateRaw(this, &SBuildActivity::CanViewLogForBuildActivity) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( LOCTEXT("BuildActivity_Cancel", "Cancel"), LOCTEXT("BuildActivity_CancelTooltip", "Cancel the pending or in progress activity"), FSlateIcon(), FUIAction( FExecuteAction::CreateRaw(this, &SBuildActivity::CancelBuildActivity), FCanExecuteAction::CreateRaw(this, &SBuildActivity::CanCancelBuildActivity) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( LOCTEXT("BuildActivity_Retry", "Retry"), LOCTEXT("BuildActivity_RetryTooltip", "Retry the failed or canceled activity"), FSlateIcon(), FUIAction( FExecuteAction::CreateRaw(this, &SBuildActivity::RetryBuildActivity), FCanExecuteAction::CreateRaw(this, &SBuildActivity::CanRetryBuildActivity) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( LOCTEXT("BuildActivity_Clear", "Clear"), LOCTEXT("BuildActivity_ClearTooltip", "Clear this activity if it is completed"), FSlateIcon(), FUIAction( FExecuteAction::CreateRaw(this, &SBuildActivity::ClearBuildActivity), FCanExecuteAction::CreateRaw(this, &SBuildActivity::CanClearBuildActivity) ), NAME_None, EUserInterfaceActionType::Button ); MenuBuilder.AddMenuEntry( LOCTEXT("BuildActivity_ClearAllCompleted", "Clear all completed"), LOCTEXT("BuildActivity_ClearAllCompletedTooltip", "Clear all completed items"), FSlateIcon(), FUIAction( FExecuteAction::CreateRaw(this, &SBuildActivity::ClearAllCompleted) ), NAME_None, EUserInterfaceActionType::Button ); return MenuBuilder.MakeWidget(); } bool SBuildActivity::CanCancelBuildActivity() const { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return false; } FBuildServiceInstance::EBuildTransferStatus Status = SelectedItems[0]->Transfer.GetStatus(); return (Status == FBuildServiceInstance::EBuildTransferStatus::Queued) || (Status == FBuildServiceInstance::EBuildTransferStatus::Active); } void SBuildActivity::CancelBuildActivity() const { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return; } SelectedItems[0]->Transfer.RequestCancel(); } bool SBuildActivity::CanRetryBuildActivity() const { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return false; } FBuildServiceInstance::EBuildTransferStatus Status = SelectedItems[0]->Transfer.GetStatus(); return (Status == FBuildServiceInstance::EBuildTransferStatus::Canceled) || (Status == FBuildServiceInstance::EBuildTransferStatus::Failed); } void SBuildActivity::RetryBuildActivity() { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return; } if (TSharedPtr ServiceInstance = BuildServiceInstance.Get()) { AddBuildTransfer(ServiceInstance->RepeatBuildTransfer(SelectedItems[0]->Transfer), SelectedItems[0]->Name, SelectedItems[0]->Platform); } } void SBuildActivity::OpenDestinationForBuildActivity() { TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return; } OpenDestinationForBuildActivity(SelectedItems[0]); } void SBuildActivity::OpenDestinationForBuildActivity(TSharedPtr Item) { using namespace UE::Zen::Build; if (TSharedPtr ServiceInstance = BuildServiceInstance.Get()) { switch (Item->Transfer.GetType()) { case FBuildServiceInstance::FBuildTransfer::EType::Oplog: { const FString Separator(TEXT("/")); FString ProjectId; FString OplogId; if (Item->Transfer.GetDestination().Split(Separator, &ProjectId, &OplogId, ESearchCase::IgnoreCase, ESearchDir::FromEnd)) { UE::Zen::FZenLocalServiceRunContext RunContext; uint16 LocalPort = 8558; if (UE::Zen::TryGetLocalServiceRunContext(RunContext)) { if (!UE::Zen::IsLocalServiceRunning(*RunContext.GetDataPath(), &LocalPort)) { UE::Zen::StartLocalService(RunContext); UE::Zen::IsLocalServiceRunning(*RunContext.GetDataPath(), &LocalPort); } } FPlatformProcess::LaunchURL(*FString::Printf(TEXT("http://localhost:%d/dashboard/?page=oplog&project=%s&oplog=%s"), LocalPort, *ProjectId, *OplogId), nullptr, nullptr); } } break; case FBuildServiceInstance::FBuildTransfer::EType::Files: { FString DestinationFolder = Item->Transfer.GetDestination(); if (FPaths::DirectoryExists(DestinationFolder)) { FPlatformProcess::ExploreFolder(*DestinationFolder); } else { FPlatformProcess::ExploreFolder(*FPaths::GetPath(DestinationFolder)); } } break; } } } bool SBuildActivity::CanClearBuildActivity() const { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return false; } FBuildServiceInstance::EBuildTransferStatus Status = SelectedItems[0]->Transfer.GetStatus(); return (Status != FBuildServiceInstance::EBuildTransferStatus::Queued) && (Status != FBuildServiceInstance::EBuildTransferStatus::Active); } void SBuildActivity::ClearBuildActivity() { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return; } BuildActivities.Remove(SelectedItems[0]); } bool SBuildActivity::CanViewLogForBuildActivity() const { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return false; } FString LogFilename = SelectedItems[0]->Transfer.GetLogFilename(); return !LogFilename.IsEmpty() && FPaths::FileExists(LogFilename); } void SBuildActivity::ViewLogForBuildActivity() const { using namespace UE::Zen::Build; TArray SelectedItems = BuildActivityListView->GetSelectedItems(); if (SelectedItems.Num() != 1) { return; } FString LogFilename = SelectedItems[0]->Transfer.GetLogFilename(); if (!LogFilename.IsEmpty() && FPaths::FileExists(LogFilename)) { FPlatformProcess::LaunchFileInDefaultExternalApplication(*LogFilename); } } void SBuildActivity::ClearAllCompleted() { using namespace UE::Zen::Build; TArray ActivitiesToRemove; for (FBuildActivityPtr BuildActivity : BuildActivities) { FBuildServiceInstance::EBuildTransferStatus Status = BuildActivity->Transfer.GetStatus(); if ((Status == FBuildServiceInstance::EBuildTransferStatus::Failed) || (Status == FBuildServiceInstance::EBuildTransferStatus::Canceled) || (Status == FBuildServiceInstance::EBuildTransferStatus::Succeeded)) { ActivitiesToRemove.AddUnique(BuildActivity); } } for (FBuildActivityPtr BuildActivity : ActivitiesToRemove) { BuildActivities.Remove(BuildActivity); } } void SBuildActivityTableRow::Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView, const FBuildActivityPtr InBuildActivity, TSharedPtr InBuildServiceInstance) { BuildActivity = InBuildActivity; BuildServiceInstance = InBuildServiceInstance; SMultiColumnTableRow::Construct(FSuperRowType::FArguments(), InOwnerTableView); } TSharedRef SBuildActivityTableRow::GenerateWidgetForColumn(const FName& ColumnName) { using namespace UE::Zen::Build; using namespace UE::BuildActivity::Internal; if (ColumnName == FBuildActivityIds::ColStatus) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(1.f,1.f) [ SNew(STextBlock) .TextStyle(&FAppStyle::GetWidgetStyle(TEXT("SmallText"))) .Text_Lambda([this]() { FString Status; if (BuildServiceInstance) { switch(BuildActivity->Transfer.GetStatus()) { case FBuildServiceInstance::EBuildTransferStatus::Invalid: return LOCTEXT("BuildActivity_TransferStatusInvalid", "Invalid"); case FBuildServiceInstance::EBuildTransferStatus::Queued: return LOCTEXT("BuildActivity_TransferStatusQueued", "Queued"); case FBuildServiceInstance::EBuildTransferStatus::Active: return LOCTEXT("BuildActivity_TransferStatusActive", "Active"); case FBuildServiceInstance::EBuildTransferStatus::Failed: return LOCTEXT("BuildActivity_TransferStatusFailed", "Failed"); case FBuildServiceInstance::EBuildTransferStatus::Canceled: return LOCTEXT("BuildActivity_TransferStatusCanceled", "Canceled"); case FBuildServiceInstance::EBuildTransferStatus::Succeeded: return LOCTEXT("BuildActivity_TransferStatusSucceeded", "Succeeded"); } } return LOCTEXT("BuildActivity_TransferStatusUnknown", "Unknown"); }) ]; } else if (ColumnName == FBuildActivityIds::ColName) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(1.f,1.f) [ SNew(STextBlock) .TextStyle(&FAppStyle::GetWidgetStyle(TEXT("SmallText"))) .Text(FText::FromString(BuildActivity->Name)) ]; } else if (ColumnName == FBuildActivityIds::ColPlatform) { return SNew(SHorizontalBox) + SHorizontalBox::Slot() .Padding(1.f,1.f) [ SNew(STextBlock) .TextStyle(&FAppStyle::GetWidgetStyle(TEXT("SmallText"))) .Text(FText::FromString(BuildActivity->Platform)) ]; } else if (ColumnName == FBuildActivityIds::ColDescription) { return SNew(SOverlay) + SOverlay::Slot() .Padding(1.f,1.f) [ SNew(SProgressBar) .Visibility_Lambda([this]() { return BuildActivity->Transfer.GetStatus() == FBuildServiceInstance::EBuildTransferStatus::Active ? EVisibility::Visible : EVisibility::Collapsed; }) .Percent_Lambda([this]() { FString Label; FString Detail; float Percent; if (BuildActivity->Transfer.GetCurrentProgress(Label, Detail, Percent)) { return Percent/100.f; } return 0.f; }) ] + SOverlay::Slot() .Padding(1.f,1.f) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text_Lambda([this]() { FString Label; FString Detail; float Percent; if (BuildActivity->Transfer.GetCurrentProgress(Label, Detail, Percent)) { return FText::FromString(Label); } return FText::FromString(FString()); }) .TextStyle(&FAppStyle::GetWidgetStyle(TEXT("SmallText"))) .Justification(ETextJustify::Type::Left) .ToolTipText_Lambda([this]() { FString Label; FString Detail; float Percent; if (BuildActivity->Transfer.GetCurrentProgress(Label, Detail, Percent)) { return FText::FromString(Detail); } return FText::FromString(FString()); }) .Visibility_Lambda([this]() { return BuildActivity->Transfer.GetStatus() == FBuildServiceInstance::EBuildTransferStatus::Active ? EVisibility::Visible : EVisibility::Collapsed; }) ]; } return SNullWidget::NullWidget; } const FSlateBrush* SBuildActivityTableRow::GetBorder() const { return STableRow::GetBorder(); } FReply SBuildActivityTableRow::OnBrowseClicked() { return FReply::Unhandled(); } #undef LOCTEXT_NAMESPACE