Skip to content

Commit baaee3c

Browse files
committed
[GEN-1863] add pagination support to ChannelOperationRequestRepository + new filtering
Add GetPaginatedAsync method with comprehensive filtering options including status, request type, node IDs, wallet ID, user ID, date range, and excluded IDs. Update interface and implement pagination UI with filters in ChannelRequests page.
1 parent b5c99c2 commit baaee3c

3 files changed

Lines changed: 282 additions & 1 deletion

File tree

src/Data/Repositories/ChannelOperationRequestRepository.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,5 +235,86 @@ public async Task<List<ChannelOperationRequest>> GetPendingRequests()
235235
.Include(x => x.Utxos).AsSplitQuery()
236236
.ToListAsync();
237237
}
238+
239+
public async Task<(List<ChannelOperationRequest> requests, int totalCount)> GetPaginatedAsync(
240+
int pageNumber,
241+
int pageSize,
242+
ChannelOperationRequestStatus? status = null,
243+
OperationRequestType? requestType = null,
244+
int? sourceNodeId = null,
245+
int? destNodeId = null,
246+
int? walletId = null,
247+
string? userId = null,
248+
DateTimeOffset? fromDate = null,
249+
DateTimeOffset? toDate = null,
250+
IEnumerable<int>? excludedIds = null)
251+
{
252+
await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync();
253+
254+
var query = applicationDbContext.ChannelOperationRequests
255+
.Include(request => request.Wallet)
256+
.Include(request => request.SourceNode)
257+
.Include(request => request.DestNode)
258+
.Include(request => request.ChannelOperationRequestPsbts)
259+
.Include(request => request.User)
260+
.Include(x => x.Utxos)
261+
.AsSplitQuery()
262+
.AsQueryable();
263+
264+
if (status.HasValue)
265+
{
266+
query = query.Where(r => r.Status == status.Value);
267+
}
268+
269+
if (requestType.HasValue)
270+
{
271+
query = query.Where(r => r.RequestType == requestType.Value);
272+
}
273+
274+
if (sourceNodeId.HasValue)
275+
{
276+
query = query.Where(r => r.SourceNodeId == sourceNodeId.Value);
277+
}
278+
279+
if (destNodeId.HasValue)
280+
{
281+
query = query.Where(r => r.DestNodeId == destNodeId.Value);
282+
}
283+
284+
if (walletId.HasValue)
285+
{
286+
query = query.Where(r => r.WalletId == walletId.Value);
287+
}
288+
289+
if (!string.IsNullOrEmpty(userId))
290+
{
291+
query = query.Where(r => r.UserId == userId);
292+
}
293+
294+
if (fromDate.HasValue)
295+
{
296+
query = query.Where(r => r.CreationDatetime >= fromDate.Value);
297+
}
298+
299+
if (toDate.HasValue)
300+
{
301+
query = query.Where(r => r.CreationDatetime <= toDate.Value);
302+
}
303+
304+
if (excludedIds != null && excludedIds.Any())
305+
{
306+
query = query.Where(r => !excludedIds.Contains(r.Id));
307+
}
308+
309+
var totalCount = await query.CountAsync();
310+
311+
var requests = await query
312+
.OrderByDescending(r => r.CreationDatetime)
313+
.Skip((pageNumber - 1) * pageSize)
314+
.Take(pageSize)
315+
.ToListAsync();
316+
317+
return (requests, totalCount);
318+
}
238319
}
239320
}

src/Data/Repositories/Interfaces/IChannelOperationRequestRepository.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,17 @@ public interface IChannelOperationRequestRepository : IBitcoinRequestRepository
4444
/// </summary>
4545
/// <returns></returns>
4646
Task<List<ChannelOperationRequest>> GetPendingRequests();
47+
48+
Task<(List<ChannelOperationRequest> requests, int totalCount)> GetPaginatedAsync(
49+
int pageNumber,
50+
int pageSize,
51+
ChannelOperationRequestStatus? status = null,
52+
OperationRequestType? requestType = null,
53+
int? sourceNodeId = null,
54+
int? destNodeId = null,
55+
int? walletId = null,
56+
string? userId = null,
57+
DateTimeOffset? fromDate = null,
58+
DateTimeOffset? toDate = null,
59+
IEnumerable<int>? excludedIds = null);
4760
}

src/Pages/ChannelRequests.razor

Lines changed: 188 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
@using Quartz
55
@using Humanizer
66
@using NBitcoin
7+
@using Blazorise.Components
78
@using NodeGuard.Jobs
89
@using Google.Protobuf
910
@using NBXplorer.Models
@@ -287,10 +288,121 @@
287288
</Tooltip>
288289
</h3>
289290
<br/>
291+
<Row Class="mb-3">
292+
<Column ColumnSize="ColumnSize.Is1">
293+
<Field>
294+
<FieldLabel>Status</FieldLabel>
295+
<Autocomplete TItem="ChannelOperationRequestStatus?"
296+
TValue="ChannelOperationRequestStatus?"
297+
Data="@_statusOptions"
298+
TextField="@(item => item?.Humanize() ?? "All")"
299+
ValueField="@(item => item)"
300+
@bind-SelectedValue="@_statusFilter"
301+
@bind-SelectedValue:after="OnFiltersChanged"
302+
Placeholder="All"
303+
FreeTyping="false"
304+
MinLength="0" />
305+
</Field>
306+
</Column>
307+
<Column ColumnSize="ColumnSize.Is1">
308+
<Field>
309+
<FieldLabel>Type</FieldLabel>
310+
<Autocomplete TItem="OperationRequestType?"
311+
TValue="OperationRequestType?"
312+
Data="@_requestTypeOptions"
313+
TextField="@(item => item?.Humanize() ?? "All")"
314+
ValueField="@(item => item)"
315+
@bind-SelectedValue="@_requestTypeFilter"
316+
@bind-SelectedValue:after="OnFiltersChanged"
317+
Placeholder="All"
318+
FreeTyping="false"
319+
MinLength="0" />
320+
</Field>
321+
</Column>
322+
<Column ColumnSize="ColumnSize.Is2">
323+
<Field>
324+
<FieldLabel>Source Node</FieldLabel>
325+
<Autocomplete TItem="Node"
326+
TValue="int?"
327+
Data="@_availableNodes"
328+
TextField="@(item => item?.Name ?? "All")"
329+
ValueField="@(item => (int?)item?.Id)"
330+
@bind-SelectedValue="@_sourceNodeFilter"
331+
@bind-SelectedValue:after="OnFiltersChanged"
332+
Placeholder="All"
333+
FreeTyping="false"
334+
MinLength="0" />
335+
</Field>
336+
</Column>
337+
<Column ColumnSize="ColumnSize.Is2">
338+
<Field>
339+
<FieldLabel>Dest Node</FieldLabel>
340+
<Autocomplete TItem="Node"
341+
TValue="int?"
342+
Data="@_availableNodes"
343+
TextField="@(item => item?.Name ?? "All")"
344+
ValueField="@(item => (int?)item?.Id)"
345+
@bind-SelectedValue="@_destNodeFilter"
346+
@bind-SelectedValue:after="OnFiltersChanged"
347+
Placeholder="All"
348+
FreeTyping="false"
349+
MinLength="0" />
350+
</Field>
351+
</Column>
352+
<Column ColumnSize="ColumnSize.Is2">
353+
<Field>
354+
<FieldLabel>Wallet</FieldLabel>
355+
<Autocomplete TItem="Wallet"
356+
TValue="int?"
357+
Data="@_allWallets"
358+
TextField="@(item => item?.Name ?? "All")"
359+
ValueField="@(item => (int?)item?.Id)"
360+
@bind-SelectedValue="@_walletFilter"
361+
@bind-SelectedValue:after="OnFiltersChanged"
362+
Placeholder="All"
363+
FreeTyping="false"
364+
MinLength="0" />
365+
</Field>
366+
</Column>
367+
<Column ColumnSize="ColumnSize.Is1">
368+
<Field>
369+
<FieldLabel>User</FieldLabel>
370+
<Autocomplete TItem="ApplicationUser"
371+
TValue="string?"
372+
Data="@_availableUsers"
373+
TextField="@(item => item?.UserName ?? "All")"
374+
ValueField="@(item => item?.Id)"
375+
@bind-SelectedValue="@_userFilter"
376+
@bind-SelectedValue:after="OnFiltersChanged"
377+
Placeholder="All"
378+
FreeTyping="false"
379+
MinLength="0" />
380+
</Field>
381+
</Column>
382+
<Column ColumnSize="ColumnSize.Is1">
383+
<Field>
384+
<FieldLabel>From</FieldLabel>
385+
<DatePicker TValue="DateTime?" @bind-Date="@_fromDate" @bind-Date:after="OnFiltersChanged" InputMode="DateInputMode.Date" Placeholder="Start" />
386+
</Field>
387+
</Column>
388+
<Column ColumnSize="ColumnSize.Is1">
389+
<Field>
390+
<FieldLabel>To</FieldLabel>
391+
<DatePicker TValue="DateTime?" @bind-Date="@_toDate" @bind-Date:after="OnFiltersChanged" InputMode="DateInputMode.Date" Placeholder="End" />
392+
</Field>
393+
</Column>
394+
<Column ColumnSize="ColumnSize.Is1" Class="d-flex align-items-end pb-3">
395+
<Button Color="Color.Secondary" Clicked="ClearAllFilters">
396+
<Icon Name="IconName.Times" /> Clear
397+
</Button>
398+
</Column>
399+
</Row>
400+
@* TODO: Convert this grid to paginated ReadData to avoid loading all records in memory. *@
290401
<DataGrid TItem="ChannelOperationRequest"
291402
@ref="@_allRequestsDatagrid"
292403
Data="@_allRequests"
293-
Filterable="true"
404+
ReadData="@OnAllRequestsReadData"
405+
TotalItems="@_totalAllRequests"
294406
ShowPager="true"
295407
ShowPageSizes="true"
296408
Striped="true">
@@ -455,10 +567,29 @@
455567
@inject IPriceConversionService PriceConversionService
456568
@inject IAuditService AuditService
457569
@inject IHttpContextAccessor HttpContextAccessor
570+
@inject IApplicationUserRepository ApplicationUserRepository
458571

459572
@code {
460573
private List<ChannelOperationRequest>? _channelRequests;
461574
private List<ChannelOperationRequest>? _allRequests;
575+
private int _totalAllRequests;
576+
private List<ApplicationUser> _availableUsers = new();
577+
private List<Node> _availableNodes = new();
578+
579+
// Filter state
580+
private ChannelOperationRequestStatus? _statusFilter;
581+
private OperationRequestType? _requestTypeFilter;
582+
private int? _sourceNodeFilter;
583+
private int? _destNodeFilter;
584+
private int? _walletFilter;
585+
private string? _userFilter;
586+
private DateTime? _fromDate;
587+
private DateTime? _toDate;
588+
589+
// Filter options
590+
private List<ChannelOperationRequestStatus?> _statusOptions = new() { null };
591+
private List<OperationRequestType?> _requestTypeOptions = new() { null };
592+
462593
private ChannelOperationRequest? _selectedRequest;
463594
private ChannelOperationRequestStatus _selectedStatus;
464595
private ChannelOperationRequest _selectedRequestForMarkingAsFailed;
@@ -1136,8 +1267,64 @@
11361267
private async Task RefreshChannelRequestsInformation()
11371268
{
11381269
await FetchRequests();
1270+
if (_allRequestsDatagrid != null)
1271+
{
1272+
await _allRequestsDatagrid.Reload();
1273+
}
11391274
StateHasChanged();
11401275
}
1276+
1277+
private async Task OnAllRequestsReadData(DataGridReadDataEventArgs<ChannelOperationRequest> e)
1278+
{
1279+
if (!e.CancellationToken.IsCancellationRequested)
1280+
{
1281+
var fromDateOffset = _fromDate.HasValue ? new DateTimeOffset(_fromDate.Value, TimeSpan.Zero) : (DateTimeOffset?)null;
1282+
var toDateOffset = _toDate.HasValue ? new DateTimeOffset(_toDate.Value.AddDays(1).AddSeconds(-1), TimeSpan.Zero) : (DateTimeOffset?)null;
1283+
1284+
var excludedIds = _channelRequests?.Select(r => r.Id) ?? Enumerable.Empty<int>();
1285+
1286+
var (requests, totalCount) = await ChannelOperationRequestRepository.GetPaginatedAsync(
1287+
e.Page,
1288+
e.PageSize,
1289+
_statusFilter,
1290+
_requestTypeFilter,
1291+
_sourceNodeFilter,
1292+
_destNodeFilter,
1293+
_walletFilter,
1294+
_userFilter,
1295+
fromDateOffset,
1296+
toDateOffset,
1297+
excludedIds);
1298+
1299+
_allRequests = requests;
1300+
_totalAllRequests = totalCount;
1301+
}
1302+
}
1303+
1304+
private async Task OnFiltersChanged()
1305+
{
1306+
if (_allRequestsDatagrid != null)
1307+
{
1308+
await _allRequestsDatagrid.Reload();
1309+
}
1310+
}
1311+
1312+
private async Task ClearAllFilters()
1313+
{
1314+
_statusFilter = null;
1315+
_requestTypeFilter = null;
1316+
_sourceNodeFilter = null;
1317+
_destNodeFilter = null;
1318+
_walletFilter = null;
1319+
_userFilter = null;
1320+
_fromDate = null;
1321+
_toDate = null;
1322+
1323+
if (_allRequestsDatagrid != null)
1324+
{
1325+
await _allRequestsDatagrid.Reload();
1326+
}
1327+
}
11411328

11421329
private async Task OnChangelessChanged(bool value)
11431330
{

0 commit comments

Comments
 (0)