> GetLastRunsAsync(
+ string tableName,
+ CancellationToken cancellationToken = default);
```
-## SQL Query Modal Component
+---
-### Features
-- Large modal (80% viewport width)
-- Monospace font display (``)
-- Basic SQL formatting (line breaks at clauses)
-- Copy to Clipboard button
-- Dynamic title based on context
+## API Controller
-### Usage
-```razor
-
-```
-
-## Schedule Section Component
-
-### Features
-- Header with schedule type name and Enabled/Disabled badge
-- Settings table showing:
- - Interval (with "Default" or "Override" indicator)
- - Pre-Purge flag
- - Re-Index flag
-- Query preview (truncated to ~100 chars)
-- "View Full Query" button
-
-### Props
```csharp
-[Parameter] public string ScheduleType { get; set; } // "Mass", "Daily", "Hourly"
-[Parameter] public ScheduleConfig? Config { get; set; }
-[Parameter] public ScheduleConfig DefaultConfig { get; set; }
-[Parameter] public string? Query { get; set; }
-[Parameter] public EventCallback OnViewQuery { get; set; }
+[ApiController]
+[Route(ApiRoutes.Pipelines.Base)]
+[Authorize]
+public class PipelineController : ControllerBase
+{
+ private readonly IEtlPipelineFactory _pipelineFactory;
+ private readonly IDataUpdateRepository _dataUpdateRepository;
+
+ [HttpGet]
+ public ActionResult GetPipelineNames()
+ {
+ var names = _pipelineFactory.GetAvailableTables()
+ .OrderBy(n => n)
+ .ToList();
+ return Ok(new PipelineListResponse(names));
+ }
+
+ [HttpGet(ApiRoutes.Pipelines.ByName)]
+ public ActionResult GetPipeline(string name)
+ {
+ // Map from internal config to DTO
+ // Truncate queries for preview, include full if admin
+ }
+
+ [HttpGet(ApiRoutes.Pipelines.Status)]
+ public async Task> GetStatus(string name)
+ {
+ // Get last runs, calculate IsOverdue using DataUpdateRepository.IsOverdue()
+ }
+
+ [HttpGet(ApiRoutes.Pipelines.Executions)]
+ public async Task> GetExecutions(
+ string name, [FromQuery] int count = 10)
+ {
+ // Get recent updates from repository
+ }
+}
```
-## API Endpoints
+---
-| Method | Route | Description |
-|--------|-------|-------------|
-| GET | `/api/pipelines` | Get all pipeline names |
-| GET | `/api/pipelines/{name}` | Get pipeline configuration |
-| GET | `/api/pipelines/{name}/status` | Get schedule status summary |
-| GET | `/api/pipelines/{name}/executions?count=10` | Get recent executions |
+## Client Implementation
+
+### PipelineApiClient.cs
+
+```csharp
+public interface IPipelineApiClient
+{
+ Task> GetPipelineNamesAsync(CancellationToken ct = default);
+ Task> GetPipelineAsync(string name, CancellationToken ct = default);
+ Task> GetStatusAsync(string name, CancellationToken ct = default);
+ Task> GetExecutionsAsync(string name, int count = 10, CancellationToken ct = default);
+}
+
+public class PipelineApiClient : ApiClientBase, IPipelineApiClient
+{
+ public PipelineApiClient(HttpClient httpClient) : base(httpClient) { }
+
+ public Task> GetPipelineNamesAsync(CancellationToken ct = default)
+ => GetAsync(ApiRoutes.Pipelines.Base, ct);
+
+ public Task> GetPipelineAsync(string name, CancellationToken ct = default)
+ => GetAsync(ApiRoutes.Pipelines.GetByName(name), ct);
+
+ public Task> GetStatusAsync(string name, CancellationToken ct = default)
+ => GetAsync(ApiRoutes.Pipelines.GetStatus(name), ct);
+
+ public Task> GetExecutionsAsync(string name, int count = 10, CancellationToken ct = default)
+ => GetAsync(ApiRoutes.Pipelines.GetExecutions(name, count), ct);
+}
+```
+
+---
+
+## Blazor Components
+
+### PipelineViewer.razor Structure
+
+```razor
+@page "/admin/pipeline-viewer"
+@attribute [Authorize]
+@inject IPipelineApiClient PipelineApi
+
+Pipeline Configuration Viewer
+
+ETL Pipeline Configuration Viewer
+
+
+
+
+@if (_config is not null)
+{
+
+
+
+
+}
+
+
+```
+
+### SqlQueryModal.razor
+
+- Large modal (80% width)
+- `` with monospace font
+- Copy to clipboard button
+- Basic SQL formatting (line breaks at clauses)
+
+### PipelineScheduleSection.razor
+
+- Props: ScheduleType (UpdateTypes), Config (PipelineScheduleDto), Query, OnViewQuery callback
+- Shows enabled badge
+- Settings table with Default/Override indicators
+- Query preview with "View Full" button
+
+---
## Implementation Notes
-### Override Detection
-To show "Default" vs "Override" for schedule settings:
+### Override Detection (in API)
+
```csharp
-bool IsOverride(int? pipelineValue, int defaultValue) =>
- pipelineValue.HasValue && pipelineValue.Value != defaultValue;
+bool IsOverride(T? pipelineValue, T defaultValue) where T : struct =>
+ pipelineValue.HasValue && !pipelineValue.Value.Equals(defaultValue);
```
-### Next Required Calculation
-```csharp
-DateTime? nextRequired = lastSuccessful?.AddMinutes(intervalMinutes);
-bool isOverdue = nextRequired.HasValue && nextRequired.Value < DateTime.UtcNow;
-```
+### Overdue Calculation (reuse existing)
-### Query Truncation
```csharp
-string Truncate(string sql, int maxLength = 100) =>
- sql.Length <= maxLength ? sql : sql[..maxLength] + "...";
+// Use DataUpdateRepository.IsOverdue() which includes 50% grace period
+var isOverdue = DataUpdateRepository.IsOverdue(
+ lastSuccessfulRun, tableName, updateType, customIntervals);
```
### Connection Type Badge Colors
-- JDE: Blue (`BadgeStyle.Info`)
-- CMS: Green (`BadgeStyle.Success`)
-- GIW: Orange (`BadgeStyle.Warning`)
+
+| Connection | Badge Style |
+|------------|-------------|
+| jde | `BadgeStyle.Info` (blue) |
+| cms | `BadgeStyle.Success` (green) |
+| giw | `BadgeStyle.Warning` (orange) |
+
+### SQL Query Truncation
+
+```csharp
+static string Truncate(string? sql, int maxLength = 100) =>
+ sql is null ? "" :
+ sql.Length <= maxLength ? sql : sql[..maxLength] + "...";
+```
+
+---
+
+## Security Considerations
+
+1. **Authorization**: Page and API require `[Authorize]`
+2. **SQL Visibility**: Full SQL/scripts only exposed if user has appropriate permissions (consider admin-only)
+3. **No write operations**: This is read-only, no mutations exposed
+
+---
## Dependencies
-- Radzen.Blazor (already installed)
-- Existing `IDataUpdateRepository`
+- Radzen.Blazor (existing)
+- Existing `IEtlPipelineFactory` and `IDataUpdateRepository`
- Existing `pipelines.json` configuration
-- Existing `UpdateTypes` enum (Hourly, Daily, Mass)
+- Existing `UpdateTypes` enum
+- Existing `ApiClientBase` pattern