feat(client): add PipelineViewer page
Add admin page for viewing ETL pipeline configurations with: - Pipeline selector dropdown (alphabetical list) - Status summary table (type, last run, success, next required, status) - Execution history table with paging (10 per page) - Source, destination, and scripts info cards - Three PipelineScheduleSection components for Mass, Daily, Hourly schedules - SQL modal integration for viewing queries and scripts
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
@page "/admin/pipeline-viewer"
|
||||
@attribute [Authorize]
|
||||
@using JdeScoping.Core.ApiContracts
|
||||
@using JdeScoping.Core.ApiContracts.Pipelines
|
||||
@using JdeScoping.Core.Models.Enums
|
||||
@inject IPipelineApiClient PipelineApi
|
||||
|
||||
<PageTitle>Pipeline Configuration Viewer - JDE Scoping Tool</PageTitle>
|
||||
|
||||
<RadzenText TextStyle="TextStyle.H4" class="rz-mb-4">ETL Pipeline Configuration Viewer</RadzenText>
|
||||
|
||||
<!-- Pipeline Selector -->
|
||||
<RadzenCard class="rz-mb-4">
|
||||
<RadzenFormField Text="Select Pipeline" Style="width: 400px;">
|
||||
<RadzenDropDown @bind-Value="_selectedPipeline" Data="@_pipelineNames" Style="width: 100%;"
|
||||
Change="@OnPipelineChanged" Placeholder="Select a pipeline..." />
|
||||
</RadzenFormField>
|
||||
</RadzenCard>
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<LoadingIndicator Message="Loading pipeline data..." />
|
||||
}
|
||||
else if (_config is not null)
|
||||
{
|
||||
<!-- Status Summary Table -->
|
||||
<RadzenCard class="rz-mb-4">
|
||||
<RadzenText TextStyle="TextStyle.H5" class="rz-mb-3">Schedule Status Summary</RadzenText>
|
||||
<RadzenDataGrid Data="@_statuses" TItem="PipelineScheduleStatusDto">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn TItem="PipelineScheduleStatusDto" Property="ScheduleType" Title="Type" Width="100px">
|
||||
<Template Context="item">@item.ScheduleType.ToString()</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineScheduleStatusDto" Title="Last Run" Width="160px">
|
||||
<Template Context="item">@(item.LastRun?.ToString("MM/dd/yyyy hh:mm tt") ?? "Never")</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineScheduleStatusDto" Title="Success?" Width="80px" TextAlign="TextAlign.Center">
|
||||
<Template Context="item">
|
||||
@if (item.LastRun.HasValue)
|
||||
{
|
||||
@if (item.LastRunWasSuccessful)
|
||||
{
|
||||
<RadzenIcon Icon="check_circle" Style="color: green;" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenIcon Icon="cancel" Style="color: red;" />
|
||||
}
|
||||
}
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineScheduleStatusDto" Title="Next Required" Width="160px">
|
||||
<Template Context="item">@(item.NextRequiredRun?.ToString("MM/dd/yyyy hh:mm tt") ?? "N/A")</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineScheduleStatusDto" Title="Status" Width="100px" TextAlign="TextAlign.Center">
|
||||
<Template Context="item">
|
||||
@if (item.IsOverdue)
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Warning" Text="Overdue" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="OK" />
|
||||
}
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
</Columns>
|
||||
</RadzenDataGrid>
|
||||
</RadzenCard>
|
||||
|
||||
<!-- Execution History Table -->
|
||||
<RadzenCard class="rz-mb-4">
|
||||
<RadzenText TextStyle="TextStyle.H5" class="rz-mb-3">Recent Execution History</RadzenText>
|
||||
<RadzenDataGrid Data="@_executions" TItem="PipelineExecutionDto" AllowSorting="true" PageSize="10" AllowPaging="true">
|
||||
<Columns>
|
||||
<RadzenDataGridColumn TItem="PipelineExecutionDto" Property="ScheduleType" Title="Type" Width="100px">
|
||||
<Template Context="item">@item.ScheduleType.ToString()</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineExecutionDto" Title="Start" Width="160px">
|
||||
<Template Context="item">@item.StartTime.ToString("MM/dd/yyyy hh:mm tt")</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineExecutionDto" Title="End" Width="160px">
|
||||
<Template Context="item">@(item.EndTime?.ToString("MM/dd/yyyy hh:mm tt") ?? "In Progress")</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineExecutionDto" Title="Duration" Width="100px">
|
||||
<Template Context="item">@FormatDuration(item.Duration)</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineExecutionDto" Property="RecordCount" Title="Records" Width="100px" TextAlign="TextAlign.Right">
|
||||
<Template Context="item">@item.RecordCount.ToString("N0")</Template>
|
||||
</RadzenDataGridColumn>
|
||||
<RadzenDataGridColumn TItem="PipelineExecutionDto" Title="Result" Width="100px" TextAlign="TextAlign.Center">
|
||||
<Template Context="item">
|
||||
@if (item.WasSuccessful)
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="Success" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Danger" Text="Failed" />
|
||||
}
|
||||
</Template>
|
||||
</RadzenDataGridColumn>
|
||||
</Columns>
|
||||
</RadzenDataGrid>
|
||||
</RadzenCard>
|
||||
|
||||
<!-- Common Pipeline Info -->
|
||||
<RadzenRow Gap="1rem" class="rz-mb-4">
|
||||
<!-- Source Card -->
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenCard Style="height: 100%;">
|
||||
<RadzenText TextStyle="TextStyle.H6" class="rz-mb-2">Source</RadzenText>
|
||||
<p><strong>Connection:</strong>
|
||||
@switch (_config.Source.Connection.ToLower())
|
||||
{
|
||||
case "jde":
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Info" Text="JDE" />
|
||||
break;
|
||||
case "cms":
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="CMS" />
|
||||
break;
|
||||
case "giw":
|
||||
<RadzenBadge BadgeStyle="BadgeStyle.Warning" Text="GIW" />
|
||||
break;
|
||||
default:
|
||||
<RadzenBadge Text="@_config.Source.Connection" />
|
||||
break;
|
||||
}
|
||||
</p>
|
||||
@if (_config.Source.Parameters.Count > 0)
|
||||
{
|
||||
<p><strong>Parameters:</strong></p>
|
||||
<ul>
|
||||
@foreach (var param in _config.Source.Parameters)
|
||||
{
|
||||
<li>@param.Name (@(param.Format ?? "default"))</li>
|
||||
}
|
||||
</ul>
|
||||
}
|
||||
</RadzenCard>
|
||||
</RadzenColumn>
|
||||
|
||||
<!-- Destination Card -->
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenCard Style="height: 100%;">
|
||||
<RadzenText TextStyle="TextStyle.H6" class="rz-mb-2">Destination</RadzenText>
|
||||
<p><strong>Table:</strong> @_config.Destination.Table</p>
|
||||
<p><strong>Operation:</strong>
|
||||
<RadzenBadge BadgeStyle="@(_config.Destination.OperationType == "BulkMerge" ? BadgeStyle.Primary : BadgeStyle.Secondary)"
|
||||
Text="@_config.Destination.OperationType" />
|
||||
</p>
|
||||
@if (_config.Destination.MatchColumns?.Count > 0)
|
||||
{
|
||||
<p><strong>Match Columns:</strong> @string.Join(", ", _config.Destination.MatchColumns)</p>
|
||||
}
|
||||
@if (_config.Destination.ExcludeFromUpdate?.Count > 0)
|
||||
{
|
||||
<p><strong>Exclude:</strong> @string.Join(", ", _config.Destination.ExcludeFromUpdate)</p>
|
||||
}
|
||||
</RadzenCard>
|
||||
</RadzenColumn>
|
||||
|
||||
<!-- Scripts Card -->
|
||||
<RadzenColumn Size="4">
|
||||
<RadzenCard Style="height: 100%;">
|
||||
<RadzenText TextStyle="TextStyle.H6" class="rz-mb-2">Scripts</RadzenText>
|
||||
<p><strong>Pre-Scripts:</strong> @_config.PreScriptCount</p>
|
||||
<p><strong>Post-Scripts:</strong> @_config.PostScriptCount</p>
|
||||
@if (_config.PreScripts?.Count > 0)
|
||||
{
|
||||
<RadzenText TextStyle="TextStyle.Subtitle2" class="rz-mt-2">Pre-Scripts:</RadzenText>
|
||||
@for (int i = 0; i < _config.PreScripts.Count; i++)
|
||||
{
|
||||
var script = _config.PreScripts[i];
|
||||
var index = i + 1;
|
||||
<div>
|
||||
<RadzenButton Text="@($"Script {index}")" Icon="code" ButtonStyle="ButtonStyle.Light" Size="ButtonSize.Small"
|
||||
Click="@(() => ShowSqlModal($"Pre-Script {index}", script))" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@if (_config.PostScripts?.Count > 0)
|
||||
{
|
||||
<RadzenText TextStyle="TextStyle.Subtitle2" class="rz-mt-2">Post-Scripts:</RadzenText>
|
||||
@for (int i = 0; i < _config.PostScripts.Count; i++)
|
||||
{
|
||||
var script = _config.PostScripts[i];
|
||||
var index = i + 1;
|
||||
<div>
|
||||
<RadzenButton Text="@($"Script {index}")" Icon="code" ButtonStyle="ButtonStyle.Light" Size="ButtonSize.Small"
|
||||
Click="@(() => ShowSqlModal($"Post-Script {index}", script))" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</RadzenCard>
|
||||
</RadzenColumn>
|
||||
</RadzenRow>
|
||||
|
||||
<!-- Schedule Sections -->
|
||||
<PipelineScheduleSection ScheduleType="UpdateTypes.Mass" Config="@_config.Schedules.Mass"
|
||||
QueryPreview="@_config.Source.MassQueryPreview" FullQuery="@_config.Source.MassQuery"
|
||||
OnViewQuery="@(q => ShowSqlModal("Mass Query", q))" />
|
||||
|
||||
<PipelineScheduleSection ScheduleType="UpdateTypes.Daily" Config="@_config.Schedules.Daily"
|
||||
QueryPreview="@_config.Source.QueryPreview" FullQuery="@_config.Source.Query"
|
||||
OnViewQuery="@(q => ShowSqlModal("Daily Query", q))" />
|
||||
|
||||
<PipelineScheduleSection ScheduleType="UpdateTypes.Hourly" Config="@_config.Schedules.Hourly"
|
||||
QueryPreview="@_config.Source.QueryPreview" FullQuery="@_config.Source.Query"
|
||||
OnViewQuery="@(q => ShowSqlModal("Hourly Query", q))" />
|
||||
}
|
||||
|
||||
<SqlQueryModal @bind-Visible="_showSqlModal" Title="@_sqlModalTitle" Sql="@_sqlModalContent" />
|
||||
|
||||
@code {
|
||||
private List<string> _pipelineNames = [];
|
||||
private string? _selectedPipeline;
|
||||
private bool _isLoading;
|
||||
private PipelineConfigDto? _config;
|
||||
private List<PipelineScheduleStatusDto> _statuses = [];
|
||||
private List<PipelineExecutionDto> _executions = [];
|
||||
|
||||
private bool _showSqlModal;
|
||||
private string? _sqlModalTitle;
|
||||
private string? _sqlModalContent;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var result = await PipelineApi.GetPipelineNamesAsync();
|
||||
if (result.IsSuccess)
|
||||
{
|
||||
_pipelineNames = result.Value.PipelineNames;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnPipelineChanged()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_selectedPipeline))
|
||||
{
|
||||
_config = null;
|
||||
_statuses = [];
|
||||
_executions = [];
|
||||
return;
|
||||
}
|
||||
|
||||
_isLoading = true;
|
||||
try
|
||||
{
|
||||
var configTask = PipelineApi.GetPipelineAsync(_selectedPipeline);
|
||||
var statusTask = PipelineApi.GetStatusAsync(_selectedPipeline);
|
||||
var executionsTask = PipelineApi.GetExecutionsAsync(_selectedPipeline);
|
||||
|
||||
await Task.WhenAll(configTask, statusTask, executionsTask);
|
||||
|
||||
var configResult = configTask.Result;
|
||||
var statusResult = statusTask.Result;
|
||||
var executionsResult = executionsTask.Result;
|
||||
|
||||
if (configResult.IsSuccess)
|
||||
_config = configResult.Value;
|
||||
|
||||
if (statusResult.IsSuccess)
|
||||
_statuses = statusResult.Value.Statuses;
|
||||
|
||||
if (executionsResult.IsSuccess)
|
||||
_executions = executionsResult.Value.Executions;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowSqlModal(string title, string sql)
|
||||
{
|
||||
_sqlModalTitle = $"{title} - {_selectedPipeline}";
|
||||
_sqlModalContent = sql;
|
||||
_showSqlModal = true;
|
||||
}
|
||||
|
||||
private static string FormatDuration(TimeSpan? duration)
|
||||
{
|
||||
if (!duration.HasValue) return "-";
|
||||
var d = duration.Value;
|
||||
if (d.TotalHours >= 1) return $"{d.Hours}h {d.Minutes}m";
|
||||
if (d.TotalMinutes >= 1) return $"{d.Minutes}m {d.Seconds}s";
|
||||
return $"{d.Seconds}s";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user