Redesign refresh status table with summary counts and detail popup, sort pipeline dropdown alphabetically
Replace 11 per-table record columns on /refresh-status with Passed/Failed summary counts and a click-to-expand detail dialog showing per-table results. Add date-range SQL query to push filtering to the database. Sort pipeline dropdown alphabetically on /data-sync/requests.
This commit is contained in:
@@ -73,6 +73,7 @@ public class ManualSyncController : ApiControllerBase
|
|||||||
public ActionResult<List<PipelineInfoViewModel>> GetPipelines()
|
public ActionResult<List<PipelineInfoViewModel>> GetPipelines()
|
||||||
{
|
{
|
||||||
var pipelines = _pipelineRegistry.GetEnabledPipelines()
|
var pipelines = _pipelineRegistry.GetEnabledPipelines()
|
||||||
|
.OrderBy(p => p.Name)
|
||||||
.Select(p => new PipelineInfoViewModel
|
.Select(p => new PipelineInfoViewModel
|
||||||
{
|
{
|
||||||
Name = p.Name,
|
Name = p.Name,
|
||||||
|
|||||||
@@ -39,33 +39,25 @@ public class RefreshStatusController : ApiControllerBase
|
|||||||
[FromQuery] DateTime maxDT,
|
[FromQuery] DateTime maxDT,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
// Get raw data updates from repository
|
// Get data updates filtered in SQL by date range (end of day for maxDT)
|
||||||
var updates = await _repository.GetLastDataUpdatesAsync(ct);
|
var updates = await _repository.GetDataUpdatesInRangeAsync(minDT, maxDT.Date.AddDays(1), ct);
|
||||||
|
|
||||||
// Filter by date range
|
|
||||||
var filtered = updates
|
|
||||||
.Where(u => u.StartDt >= minDT && u.StartDt <= maxDT.AddDays(1))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
// Group by StartDt (rounded to minute) to aggregate multiple table updates into single rows
|
// Group by StartDt (rounded to minute) to aggregate multiple table updates into single rows
|
||||||
var aggregated = filtered
|
var aggregated = updates
|
||||||
.GroupBy(u => new DateTime(u.StartDt.Year, u.StartDt.Month, u.StartDt.Day, u.StartDt.Hour, u.StartDt.Minute, 0))
|
.GroupBy(u => new DateTime(u.StartDt.Year, u.StartDt.Month, u.StartDt.Day, u.StartDt.Hour, u.StartDt.Minute, 0))
|
||||||
.Select(g => new DataUpdateDto
|
.Select(g => new DataUpdateDto
|
||||||
{
|
{
|
||||||
StartDt = g.Key,
|
StartDt = g.Key,
|
||||||
EndDt = g.Max(u => u.EndDt),
|
EndDt = g.Max(u => u.EndDt),
|
||||||
WasSuccessful = g.All(u => u.WasSuccessful),
|
WasSuccessful = g.All(u => u.WasSuccessful),
|
||||||
BranchRecords = (int)(g.FirstOrDefault(u => u.TableName == "Branch")?.NumberRecords ?? 0),
|
PassedCount = g.Count(u => u.WasSuccessful),
|
||||||
ProfitCenterRecords = (int)(g.FirstOrDefault(u => u.TableName == "ProfitCenter")?.NumberRecords ?? 0),
|
FailedCount = g.Count(u => !u.WasSuccessful),
|
||||||
WorkCenterRecords = (int)(g.FirstOrDefault(u => u.TableName == "WorkCenter")?.NumberRecords ?? 0),
|
Items = g.Select(u => new DataUpdateItemDto
|
||||||
OrgHierarchyRecords = (int)(g.FirstOrDefault(u => u.TableName == "OrgHierarchy")?.NumberRecords ?? 0),
|
{
|
||||||
StatusCodeRecords = (int)(g.FirstOrDefault(u => u.TableName == "StatusCode")?.NumberRecords ?? 0),
|
TableName = u.TableName,
|
||||||
UserRecords = (int)(g.FirstOrDefault(u => u.TableName == "JdeUser")?.NumberRecords ?? 0),
|
WasSuccessful = u.WasSuccessful,
|
||||||
ItemRecords = (int)(g.FirstOrDefault(u => u.TableName == "Item")?.NumberRecords ?? 0),
|
NumberRecords = u.NumberRecords
|
||||||
LotRecords = (int)(g.FirstOrDefault(u => u.TableName == "Lot")?.NumberRecords ?? 0),
|
}).OrderBy(i => i.TableName).ToList()
|
||||||
WorkOrderRecords = (int)(g.FirstOrDefault(u => u.TableName.Contains("WorkOrder_"))?.NumberRecords ?? 0),
|
|
||||||
WorkOrderStepRecords = (int)(g.FirstOrDefault(u => u.TableName.Contains("WorkOrderStep"))?.NumberRecords ?? 0),
|
|
||||||
WorkOrderComponentRecords = (int)(g.FirstOrDefault(u => u.TableName.Contains("WorkOrderComponent"))?.NumberRecords ?? 0)
|
|
||||||
})
|
})
|
||||||
.OrderByDescending(d => d.StartDt)
|
.OrderByDescending(d => d.StartDt)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
@*
|
||||||
|
RefreshStatusDetailDialog.razor - Per-table detail popup for a sync run.
|
||||||
|
|
||||||
|
Displays a grid of individual table sync results (table name, record count, pass/fail).
|
||||||
|
*@
|
||||||
|
|
||||||
|
<RadzenText TextStyle="TextStyle.Subtitle1" class="rz-mb-2">
|
||||||
|
@SyncRun.StartDt.ToString("MM/dd/yyyy hh:mm tt") — @(SyncRun.EndDt?.ToString("hh:mm tt") ?? "In Progress")
|
||||||
|
</RadzenText>
|
||||||
|
|
||||||
|
<RadzenDataGrid Data="@SyncRun.Items" TItem="DataUpdateItemDto" AllowSorting="true" Style="text-align: center;">
|
||||||
|
<Columns>
|
||||||
|
<RadzenDataGridColumn TItem="DataUpdateItemDto" Property="TableName" Title="Table" Width="200px" TextAlign="TextAlign.Left" SortOrder="SortOrder.Ascending" />
|
||||||
|
<RadzenDataGridColumn TItem="DataUpdateItemDto" Property="NumberRecords" Title="Records" Width="120px" TextAlign="TextAlign.Center" />
|
||||||
|
<RadzenDataGridColumn TItem="DataUpdateItemDto" Property="WasSuccessful" Title="Status" Width="100px" TextAlign="TextAlign.Center">
|
||||||
|
<Template Context="item">
|
||||||
|
@if (item.WasSuccessful)
|
||||||
|
{
|
||||||
|
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="PASS" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<RadzenBadge BadgeStyle="BadgeStyle.Danger" Text="FAIL" />
|
||||||
|
}
|
||||||
|
</Template>
|
||||||
|
</RadzenDataGridColumn>
|
||||||
|
</Columns>
|
||||||
|
</RadzenDataGrid>
|
||||||
|
|
||||||
|
<div class="rz-mt-4" style="text-align: right;">
|
||||||
|
<RadzenButton Text="Close" ButtonStyle="ButtonStyle.Light" Click="@(() => DialogService.Close())" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
[Parameter] public DataUpdateDto SyncRun { get; set; } = default!;
|
||||||
|
|
||||||
|
[Inject] private DialogService DialogService { get; set; } = default!;
|
||||||
|
}
|
||||||
@@ -2,11 +2,12 @@
|
|||||||
RefreshStatus.razor - Data cache refresh status dashboard.
|
RefreshStatus.razor - Data cache refresh status dashboard.
|
||||||
|
|
||||||
Shows the status of JDE/CMS data synchronization jobs (hourly, daily, mass).
|
Shows the status of JDE/CMS data synchronization jobs (hourly, daily, mass).
|
||||||
Allows filtering by date range and entity name.
|
Allows filtering by date range. Click a row to see per-table detail.
|
||||||
*@
|
*@
|
||||||
@page "/refresh-status"
|
@page "/refresh-status"
|
||||||
@attribute [Authorize]
|
@attribute [Authorize]
|
||||||
@inject IRefreshStatusService RefreshStatusService
|
@inject IRefreshStatusService RefreshStatusService
|
||||||
|
@inject DialogService DialogService
|
||||||
|
|
||||||
<PageTitle>Cache Refresh Status - JDE Scoping Tool</PageTitle>
|
<PageTitle>Cache Refresh Status - JDE Scoping Tool</PageTitle>
|
||||||
|
|
||||||
@@ -38,38 +39,30 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<RadzenDataGrid Data="@_results" TItem="DataUpdateDto" AllowSorting="true" AllowPaging="true" PageSize="20"
|
<RadzenDataGrid Data="@_results" TItem="DataUpdateDto" AllowSorting="true" AllowPaging="true" PageSize="20"
|
||||||
PagerHorizontalAlign="HorizontalAlign.Center" AllowColumnResize="true" Style="text-align: center;">
|
PagerHorizontalAlign="HorizontalAlign.Center" AllowColumnResize="true" Style="text-align: center;"
|
||||||
|
SelectionMode="DataGridSelectionMode.Single" RowSelect="@OnRowSelect">
|
||||||
<Columns>
|
<Columns>
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="StartDT" Title="Start" Width="160px">
|
<RadzenDataGridColumn TItem="DataUpdateDto" Property="StartDt" Title="Start" Width="180px">
|
||||||
<Template Context="item">
|
<Template Context="item">
|
||||||
@item.StartDt.ToString("MM/dd/yyyy hh:mm tt")
|
@item.StartDt.ToString("MM/dd/yyyy hh:mm tt")
|
||||||
</Template>
|
</Template>
|
||||||
</RadzenDataGridColumn>
|
</RadzenDataGridColumn>
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="EndDT" Title="End" Width="160px">
|
<RadzenDataGridColumn TItem="DataUpdateDto" Property="EndDt" Title="End" Width="180px">
|
||||||
<Template Context="item">
|
<Template Context="item">
|
||||||
@(item.EndDt?.ToString("MM/dd/yyyy hh:mm tt") ?? "")
|
@(item.EndDt?.ToString("MM/dd/yyyy hh:mm tt") ?? "")
|
||||||
</Template>
|
</Template>
|
||||||
</RadzenDataGridColumn>
|
</RadzenDataGridColumn>
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="BranchRecords" Title="Branch" Width="80px" TextAlign="TextAlign.Center" />
|
<RadzenDataGridColumn TItem="DataUpdateDto" Property="PassedCount" Title="Passed" Width="100px" TextAlign="TextAlign.Center" />
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="ProfitCenterRecords" Title="Profit Center" Width="100px" TextAlign="TextAlign.Center" />
|
<RadzenDataGridColumn TItem="DataUpdateDto" Property="FailedCount" Title="Failed" Width="100px" TextAlign="TextAlign.Center" />
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkCenterRecords" Title="Work Center" Width="100px" TextAlign="TextAlign.Center" />
|
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WasSuccessful" Title="Status" Width="120px" TextAlign="TextAlign.Center">
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="OrgHierarchyRecords" Title="Org Hierarchy" Width="100px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="StatusCodeRecords" Title="Status Code" Width="100px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="UserRecords" Title="User" Width="80px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="ItemRecords" Title="Item" Width="80px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="LotRecords" Title="Lot" Width="80px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkOrderRecords" Title="Work Order" Width="100px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkOrderStepRecords" Title="WO Step" Width="90px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WorkOrderComponentRecords" Title="WO Component" Width="110px" TextAlign="TextAlign.Center" />
|
|
||||||
<RadzenDataGridColumn TItem="DataUpdateDto" Property="WasSuccessful" Title="Was Successful?" Width="120px" TextAlign="TextAlign.Center">
|
|
||||||
<Template Context="item">
|
<Template Context="item">
|
||||||
@if (item.WasSuccessful)
|
@if (item.WasSuccessful)
|
||||||
{
|
{
|
||||||
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="YES" />
|
<RadzenBadge BadgeStyle="BadgeStyle.Success" Text="PASS" />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<RadzenBadge BadgeStyle="BadgeStyle.Danger" Text="NO" />
|
<RadzenBadge BadgeStyle="BadgeStyle.Danger" Text="FAIL" />
|
||||||
}
|
}
|
||||||
</Template>
|
</Template>
|
||||||
</RadzenDataGridColumn>
|
</RadzenDataGridColumn>
|
||||||
@@ -96,7 +89,6 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
_results = await RefreshStatusService.GetRefreshStatusAsync(_minDt, _maxDt);
|
_results = await RefreshStatusService.GetRefreshStatusAsync(_minDt, _maxDt);
|
||||||
// Sort by StartDT descending
|
|
||||||
_results = _results.OrderByDescending(r => r.StartDt).ToList();
|
_results = _results.OrderByDescending(r => r.StartDt).ToList();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -104,4 +96,12 @@ else
|
|||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnRowSelect(DataUpdateDto row)
|
||||||
|
{
|
||||||
|
await DialogService.OpenAsync<JdeScoping.Client.Components.RefreshStatus.RefreshStatusDetailDialog>(
|
||||||
|
"Sync Run Detail",
|
||||||
|
new Dictionary<string, object> { { "SyncRun", row } },
|
||||||
|
new DialogOptions { Width = "600px", Resizable = true, Draggable = true });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,4 +13,13 @@ public partial interface ILotFinderRepository
|
|||||||
/// <param name="ct">Cancellation token.</param>
|
/// <param name="ct">Cancellation token.</param>
|
||||||
/// <returns>Latest data updates.</returns>
|
/// <returns>Latest data updates.</returns>
|
||||||
Task<List<DataUpdate>> GetLastDataUpdatesAsync(CancellationToken ct = default);
|
Task<List<DataUpdate>> GetLastDataUpdatesAsync(CancellationToken ct = default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all data update records within a date range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="minDt">Start of range (inclusive).</param>
|
||||||
|
/// <param name="maxDt">End of range (inclusive, end of day).</param>
|
||||||
|
/// <param name="ct">Cancellation token.</param>
|
||||||
|
/// <returns>Data updates within the range.</returns>
|
||||||
|
Task<List<DataUpdate>> GetDataUpdatesInRangeAsync(DateTime minDt, DateTime maxDt, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,38 +2,25 @@ namespace JdeScoping.Core.Models.Infrastructure;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// DTO for data refresh/sync status display.
|
/// DTO for data refresh/sync status display.
|
||||||
/// Aggregates record counts from multiple table updates into a single row.
|
/// Summarises a single sync run with pass/fail counts and per-table detail.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DataUpdateDto
|
public class DataUpdateDto
|
||||||
{
|
{
|
||||||
/// <summary>The start time of the data update.</summary>
|
/// <summary>The start time of the data update.</summary>
|
||||||
public DateTime StartDt { get; set; }
|
public DateTime StartDt { get; set; }
|
||||||
|
|
||||||
/// <summary>The end time of the data update.</summary>
|
/// <summary>The end time of the data update.</summary>
|
||||||
public DateTime? EndDt { get; set; }
|
public DateTime? EndDt { get; set; }
|
||||||
|
|
||||||
/// <summary>The number of branch records updated.</summary>
|
/// <summary>Whether the data update was successful overall.</summary>
|
||||||
public int BranchRecords { get; set; }
|
|
||||||
/// <summary>The number of profit center records updated.</summary>
|
|
||||||
public int ProfitCenterRecords { get; set; }
|
|
||||||
/// <summary>The number of work center records updated.</summary>
|
|
||||||
public int WorkCenterRecords { get; set; }
|
|
||||||
/// <summary>The number of organizational hierarchy records updated.</summary>
|
|
||||||
public int OrgHierarchyRecords { get; set; }
|
|
||||||
/// <summary>The number of status code records updated.</summary>
|
|
||||||
public int StatusCodeRecords { get; set; }
|
|
||||||
/// <summary>The number of user records updated.</summary>
|
|
||||||
public int UserRecords { get; set; }
|
|
||||||
/// <summary>The number of item records updated.</summary>
|
|
||||||
public int ItemRecords { get; set; }
|
|
||||||
/// <summary>The number of lot records updated.</summary>
|
|
||||||
public int LotRecords { get; set; }
|
|
||||||
/// <summary>The number of work order records updated.</summary>
|
|
||||||
public int WorkOrderRecords { get; set; }
|
|
||||||
/// <summary>The number of work order step records updated.</summary>
|
|
||||||
public int WorkOrderStepRecords { get; set; }
|
|
||||||
/// <summary>The number of work order component records updated.</summary>
|
|
||||||
public int WorkOrderComponentRecords { get; set; }
|
|
||||||
|
|
||||||
/// <summary>Whether the data update was successful.</summary>
|
|
||||||
public bool WasSuccessful { get; set; }
|
public bool WasSuccessful { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Number of table syncs that succeeded.</summary>
|
||||||
|
public int PassedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Number of table syncs that failed.</summary>
|
||||||
|
public int FailedCount { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Per-table detail for this sync run.</summary>
|
||||||
|
public List<DataUpdateItemDto> Items { get; set; } = [];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
namespace JdeScoping.Core.Models.Infrastructure;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per-table detail within a data sync run.
|
||||||
|
/// </summary>
|
||||||
|
public class DataUpdateItemDto
|
||||||
|
{
|
||||||
|
/// <summary>The cache table that was synced.</summary>
|
||||||
|
public string TableName { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>Whether this table sync succeeded.</summary>
|
||||||
|
public bool WasSuccessful { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Number of records synced for this table.</summary>
|
||||||
|
public long NumberRecords { get; set; }
|
||||||
|
}
|
||||||
@@ -24,4 +24,21 @@ public static partial class LotFinderQueries
|
|||||||
cte.NumberRecords
|
cte.NumberRecords
|
||||||
FROM DU_CTE cte
|
FROM DU_CTE cte
|
||||||
WHERE cte.RN = 1";
|
WHERE cte.RN = 1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all data update records within a date range, ordered by StartDT descending.
|
||||||
|
/// </summary>
|
||||||
|
public const string SqlGetDataUpdatesInRange = @"
|
||||||
|
SELECT du.SourceSystem,
|
||||||
|
du.SourceData,
|
||||||
|
du.TableName,
|
||||||
|
du.StartDT,
|
||||||
|
du.EndDT,
|
||||||
|
du.UpdateType,
|
||||||
|
du.WasSuccessful,
|
||||||
|
du.NumberRecords
|
||||||
|
FROM dbo.DataUpdate AS du
|
||||||
|
WHERE du.StartDT >= @minDt
|
||||||
|
AND du.StartDT <= @maxDt
|
||||||
|
ORDER BY du.StartDT DESC";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,4 +20,17 @@ public partial class LotFinderRepository
|
|||||||
commandTimeout: _options.Value.DefaultTimeoutSeconds)).ToList(),
|
commandTimeout: _options.Value.DefaultTimeoutSeconds)).ToList(),
|
||||||
ct);
|
ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public async Task<List<DataUpdate>> GetDataUpdatesInRangeAsync(DateTime minDt, DateTime maxDt, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
return await ExecuteQueryAsync(
|
||||||
|
nameof(GetDataUpdatesInRangeAsync),
|
||||||
|
"SQL_GET_DATA_UPDATES_IN_RANGE",
|
||||||
|
async connection => (await connection.QueryAsync<DataUpdate>(
|
||||||
|
LotFinderQueries.SqlGetDataUpdatesInRange,
|
||||||
|
new { minDt, maxDt },
|
||||||
|
commandTimeout: _options.Value.DefaultTimeoutSeconds)).ToList(),
|
||||||
|
ct);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,11 +111,13 @@ public class ManualSyncControllerTests
|
|||||||
var okResult = (OkObjectResult)result.Result!;
|
var okResult = (OkObjectResult)result.Result!;
|
||||||
var viewModels = okResult.Value.ShouldBeAssignableTo<List<PipelineInfoViewModel>>()!;
|
var viewModels = okResult.Value.ShouldBeAssignableTo<List<PipelineInfoViewModel>>()!;
|
||||||
viewModels.Count.ShouldBe(2);
|
viewModels.Count.ShouldBe(2);
|
||||||
viewModels[0].Name.ShouldBe("WorkOrders");
|
viewModels[0].Name.ShouldBe("Items");
|
||||||
viewModels[0].SupportedSyncTypes.ShouldContain("mass");
|
viewModels[0].SupportedSyncTypes.ShouldContain("mass");
|
||||||
viewModels[0].SupportedSyncTypes.ShouldContain("daily");
|
viewModels[0].SupportedSyncTypes.ShouldContain("daily");
|
||||||
viewModels[0].SupportedSyncTypes.ShouldContain("hourly");
|
viewModels[1].Name.ShouldBe("WorkOrders");
|
||||||
viewModels[1].Name.ShouldBe("Items");
|
viewModels[1].SupportedSyncTypes.ShouldContain("mass");
|
||||||
|
viewModels[1].SupportedSyncTypes.ShouldContain("daily");
|
||||||
|
viewModels[1].SupportedSyncTypes.ShouldContain("hourly");
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
Reference in New Issue
Block a user