Do not always provide counting in list views (#3696)

This commit is contained in:
Nicolas Dorier 2022-05-02 16:35:28 +09:00 committed by GitHub
parent a40429e219
commit e16a718bde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 66 additions and 90 deletions

View file

@ -836,37 +836,6 @@ namespace BTCPayServer.Tests
$"enddate:{time.AddSeconds(-1).ToString("yyyy-MM-dd HH:mm:ss")}");
}
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanListNotifications()
{
using var tester = CreateServerTester();
await tester.StartAsync();
var acc = tester.NewAccount();
acc.GrantAccess(true);
acc.RegisterDerivationScheme("BTC");
const string newVersion = "1.0.4.4";
var ctrl = acc.GetController<UINotificationsController>();
var resp = await ctrl.Generate(newVersion);
var vm = Assert.IsType<Models.NotificationViewModels.IndexViewModel>(
Assert.IsType<ViewResult>(await ctrl.Index()).Model);
Assert.True(vm.Skip == 0);
Assert.True(vm.Count == 50);
Assert.True(vm.Total == 1);
Assert.True(vm.Items.Count == 1);
var fn = vm.Items.First();
var now = DateTimeOffset.UtcNow;
Assert.True(fn.Created >= now.AddSeconds(-3));
Assert.True(fn.Created <= now);
Assert.Equal($"New version {newVersion} released!", fn.Body);
Assert.Equal($"https://github.com/btcpayserver/btcpayserver/releases/tag/v{newVersion}", fn.ActionLink);
Assert.False(fn.Seen);
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CanGetRates()
@ -2450,7 +2419,7 @@ namespace BTCPayServer.Tests
Assert.True(vm.Skip == 0);
Assert.True(vm.Count == 50);
Assert.True(vm.Total == 1);
Assert.Null(vm.Total);
Assert.True(vm.Items.Count == 1);
var fn = vm.Items.First();

View file

@ -1,39 +1,45 @@
@model BasePagingViewModel
@{
var pageSizeOptions = new [] { 50, 100, 250, 500 };
var pageSizeOptions = new[] { 50, 100, 250, 500 };
}
@if (Model.Total > pageSizeOptions.Min())
@if (Model.Total is null || Model.Total.Value > pageSizeOptions.Min())
{
<nav aria-label="..." class="w-100 clearfix">
@if (Model.Total > Model.Count)
@if (Model.Total is null || Model.Total.Value > Model.Count)
{
<ul class="pagination float-start">
<li class="page-item @(Model.Skip == 0 ? "disabled" : null)">
<a class="page-link" tabindex="-1" href="@NavigatePages(-1, Model.Count)">&laquo;</a>
</li>
<li class="page-item disabled">
@if (Model.Total <= Model.Count)
@if (Model.Total is null)
{
<span class="page-link">
@(Model.Skip + 1)@(Model.Skip + Model.Count)
</span>
}
else if (Model.Total.Value <= Model.Count)
{
<span class="page-link">
1@Model.Total
1@Model.Total.Value
</span>
}
else
{
<span class="page-link">
@(Model.Skip + 1)@(Model.Skip + Model.Count), Total: @Model.Total
@(Model.Skip + 1)@(Model.Skip + Model.Count), Total: @Model.Total.Value
</span>
}
</li>
<li class="page-item @(Model.Total > (Model.Skip + Model.Count) ? null : "disabled")">
<li class="page-item @(((Model.Total is null && Model.CurrentPageCount >= Model.Count) || (Model.Total is not null && Model.Total.Value > (Model.Skip + Model.Count))) ? null : "disabled")">
<a class="page-link" href="@NavigatePages(1, Model.Count)">&raquo;</a>
</li>
</ul>
}
@if (Model.Total >= pageSizeOptions.Min())
@if (Model.Total is null || Model.Total.Value >= pageSizeOptions.Min())
{
<ul class="pagination float-end">
<li class="page-item disabled">
@ -41,7 +47,7 @@
</li>
@foreach (int pageSize in pageSizeOptions)
{
if (Model.Total >= pageSize)
if (Model.Total is null || Model.Total.Value >= pageSize)
{
<li class="page-item @(Model.Count == pageSize ? "active" : null)">
<a class="page-link" href="@NavigatePages(0, pageSize)">@pageSize</a>

View file

@ -38,7 +38,7 @@ namespace BTCPayServer.Controllers.Greenfield
{
var prs = await _paymentRequestRepository.FindPaymentRequests(
new PaymentRequestQuery() { StoreId = storeId, IncludeArchived = includeArchived });
return Ok(prs.Items.Select(FromModel));
return Ok(prs.Select(FromModel));
}
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
@ -48,12 +48,12 @@ namespace BTCPayServer.Controllers.Greenfield
var pr = await _paymentRequestRepository.FindPaymentRequests(
new PaymentRequestQuery() { StoreId = storeId, Ids = new[] { paymentRequestId } });
if (pr.Total == 0)
if (pr.Length == 0)
{
return PaymentRequestNotFound();
}
return Ok(FromModel(pr.Items.First()));
return Ok(FromModel(pr.First()));
}
[Authorize(Policy = Policies.CanModifyPaymentRequests,
@ -63,12 +63,12 @@ namespace BTCPayServer.Controllers.Greenfield
{
var pr = await _paymentRequestRepository.FindPaymentRequests(
new PaymentRequestQuery() { StoreId = storeId, Ids = new[] { paymentRequestId }, IncludeArchived = false });
if (pr.Total == 0)
if (pr.Length == 0)
{
return PaymentRequestNotFound();
}
var updatedPr = pr.Items.First();
var updatedPr = pr.First();
updatedPr.Archived = true;
await _paymentRequestRepository.CreateOrUpdatePaymentRequest(updatedPr);
return Ok();
@ -111,12 +111,12 @@ namespace BTCPayServer.Controllers.Greenfield
request.Currency ??= StoreData.GetStoreBlob().DefaultCurrency;
var pr = await _paymentRequestRepository.FindPaymentRequests(
new PaymentRequestQuery() { StoreId = storeId, Ids = new[] { paymentRequestId } });
if (pr.Total == 0)
if (pr.Length == 0)
{
return PaymentRequestNotFound();
}
var updatedPr = pr.Items.First();
var updatedPr = pr.First();
updatedPr.SetBlob(request);
return Ok(FromModel(await _paymentRequestRepository.CreateOrUpdatePaymentRequest(updatedPr)));

View file

@ -821,7 +821,6 @@ namespace BTCPayServer.Controllers
InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
invoiceQuery.StoreId = model.StoreIds;
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
invoiceQuery.Take = model.Count;
invoiceQuery.Skip = model.Skip;
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
@ -845,7 +844,6 @@ namespace BTCPayServer.Controllers
Details = InvoicePopulatePayments(invoice),
});
}
model.Total = await counting;
return View(model);
}

View file

@ -120,15 +120,6 @@ namespace BTCPayServer.Controllers
return View(model);
}
[HttpGet]
public async Task<IActionResult> Generate(string version)
{
if (_env.NetworkType != NBitcoin.ChainName.Regtest)
return NotFound();
await _notificationSender.SendNotification(new AdminScope(), new NewVersionNotification(version));
return RedirectToAction(nameof(Index));
}
[HttpPost]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanManageNotificationsForUser)]
public async Task<IActionResult> FlipRead(string id)

View file

@ -74,8 +74,7 @@ namespace BTCPayServer.Controllers
IncludeArchived = includeArchived
});
model.Total = result.Total;
model.Items = result.Items.Select(data => new ViewPaymentRequestViewModel(data)).ToList();
model.Items = result.Select(data => new ViewPaymentRequestViewModel(data)).ToList();
return View(model);
}

View file

@ -68,7 +68,6 @@ namespace BTCPayServer.Controllers
Disabled = u.LockoutEnabled && u.LockoutEnd != null && DateTimeOffset.UtcNow < u.LockoutEnd.Value.UtcDateTime
})
.ToListAsync();
model.Total = await usersQuery.CountAsync();
return View(model);
}

View file

@ -192,7 +192,7 @@ namespace BTCPayServer.Controllers
var vm = this.ParseListQuery(new PullPaymentsModel
{
Skip = skip, Count = count, Total = await ppsQuery.CountAsync(), ActiveState = pullPaymentState
Skip = skip, Count = count, ActiveState = pullPaymentState
});
switch (pullPaymentState)
@ -533,11 +533,11 @@ namespace BTCPayServer.Controllers
.ToDictionary(pair => pair.Key, pair => pair.Value);
payoutRequest = payoutRequest.Where(p => p.State == vm.PayoutState);
vm.Total = await payoutRequest.CountAsync();
payoutRequest = payoutRequest.Skip(vm.Skip).Take(vm.Count);
var payouts = await payoutRequest.OrderByDescending(p => p.Date)
.Select(o => new { Payout = o, PullPayment = o.PullPaymentData }).ToListAsync();
foreach (var item in payouts)
{
var ppBlob = item.PullPayment?.GetBlob();

View file

@ -281,7 +281,7 @@ namespace BTCPayServer.Controllers
var transactions = await wallet.FetchTransactions(paymentMethod.AccountDerivation);
var walletBlob = await walletBlobAsync;
var walletTransactionsInfo = await walletTransactionsInfoAsync;
var model = new ListTransactionsViewModel { Skip = skip, Count = count, Total = 0 };
var model = new ListTransactionsViewModel { Skip = skip, Count = count };
if (labelFilter != null)
{
model.PaginationQuery = new Dictionary<string, object> { { "labelFilter", labelFilter } };

View file

@ -8,6 +8,7 @@ using BTCPayServer.Data;
using BTCPayServer.Logging;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -58,7 +59,7 @@ namespace BTCPayServer.HostedServices
}
var invoiceQuery = new InvoiceQuery { IncludeArchived = true };
var totalCount = await _invoiceRepository.GetInvoicesTotal(invoiceQuery);
var totalCount = await CountInvoices();
const int PAGE_SIZE = 1000;
var totalPages = Math.Ceiling(totalCount * 1.0m / PAGE_SIZE);
Logs.PayServer.LogInformation($"Importing {totalCount} invoices into the search table in {totalPages - startFromPage} pages");
@ -118,5 +119,11 @@ namespace BTCPayServer.HostedServices
}
Logs.PayServer.LogInformation($"Full invoice search import successful");
}
private Task<int> CountInvoices()
{
using var ctx = _dbContextFactory.CreateContext();
return ctx.Invoices.CountAsync();
}
}
}

View file

@ -7,10 +7,12 @@ namespace BTCPayServer.Models
{
public int Skip { get; set; } = 0;
public int Count { get; set; } = 50;
public int Total { get; set; }
public int? Total { get; set; }
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string SearchTerm { get; set; }
public int? TimezoneOffset { get; set; }
public Dictionary<string, object> PaginationQuery { get; set; }
public abstract int CurrentPageCount { get; }
}
}

View file

@ -8,6 +8,7 @@ namespace BTCPayServer.Models.InvoicingModels
public class InvoicesModel : BasePagingViewModel
{
public List<InvoiceModel> Invoices { get; set; } = new List<InvoiceModel>();
public override int CurrentPageCount => Invoices.Count;
public string[] StoreIds { get; set; }
public string StoreId { get; set; }
public bool IncludeArchived { get; set; }

View file

@ -6,5 +6,7 @@ namespace BTCPayServer.Models.NotificationViewModels
public class IndexViewModel : BasePagingViewModel
{
public List<NotificationViewModel> Items { get; set; }
public override int CurrentPageCount => Items.Count;
}
}

View file

@ -13,6 +13,7 @@ namespace BTCPayServer.Models.PaymentRequestViewModels
public class ListPaymentRequestsViewModel : BasePagingViewModel
{
public List<ViewPaymentRequestViewModel> Items { get; set; }
public override int CurrentPageCount => Items.Count;
}
public class UpdatePaymentRequestViewModel

View file

@ -17,6 +17,7 @@ namespace BTCPayServer.Models.ServerViewModels
public IEnumerable<string> Roles { get; set; }
}
public List<UserViewModel> Users { get; set; } = new List<UserViewModel>();
public override int CurrentPageCount => Users.Count;
public Dictionary<string, string> Roles { get; set; }
}

View file

@ -19,6 +19,7 @@ namespace BTCPayServer.Models.WalletViewModels
}
public HashSet<ColoredLabel> Labels { get; set; } = new HashSet<ColoredLabel>();
public List<TransactionViewModel> Transactions { get; set; } = new List<TransactionViewModel>();
public override int CurrentPageCount => Transactions.Count;
public string CryptoCode { get; set; }
}
}

View file

@ -15,6 +15,7 @@ namespace BTCPayServer.Models.WalletViewModels
public string PaymentMethodId { get; set; }
public List<PayoutModel> Payouts { get; set; }
public override int CurrentPageCount => Payouts.Count;
public IEnumerable<PaymentMethodId> PaymentMethods { get; set; }
public PayoutState PayoutState { get; set; }
public string PullPaymentName { get; set; }

View file

@ -33,6 +33,7 @@ namespace BTCPayServer.Models.WalletViewModels
}
public List<PullPaymentModel> PullPayments { get; set; } = new List<PullPaymentModel>();
public override int CurrentPageCount => PullPayments.Count;
public string PaymentMethodId { get; set; }
public IEnumerable<PaymentMethodId> PaymentMethods { get; set; }
public PullPaymentState ActiveState { get; set; } = PullPaymentState.Active;

View file

@ -122,12 +122,11 @@ namespace BTCPayServer.PaymentRequest
private async Task CheckingPendingPayments(CancellationToken cancellationToken)
{
Logs.PayServer.LogInformation("Starting payment request expiration watcher");
var (total, items) = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
var items = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
{
Status = new[] { Client.Models.PaymentRequestData.PaymentRequestStatus.Pending }
}, cancellationToken);
Logs.PayServer.LogInformation($"{total} pending payment requests being checked since last run");
Logs.PayServer.LogInformation($"{items.Length} pending payment requests being checked since last run");
await Task.WhenAll(items.Select(i => _PaymentRequestService.UpdatePaymentRequestStateIfNeeded(i))
.ToArray());
}

View file

@ -736,13 +736,6 @@ namespace BTCPayServer.Services.Invoices
return query;
}
public async Task<int> GetInvoicesTotal(InvoiceQuery queryObject)
{
using var context = _applicationDbContextFactory.CreateContext();
var query = GetInvoiceQuery(context, queryObject);
return await query.CountAsync();
}
public async Task<InvoiceEntity[]> GetInvoices(InvoiceQuery queryObject)
{
using var context = _applicationDbContextFactory.CreateContext();

View file

@ -48,7 +48,7 @@ namespace BTCPayServer.Services.Notifications
UserId = userId
});
entry.SetAbsoluteExpiration(TimeSpan.FromMilliseconds(_cacheExpiryMs));
var res = new NotificationsViewModel { Last5 = resp.Items, UnseenCount = resp.Count };
var res = new NotificationsViewModel { Last5 = resp.Items, UnseenCount = resp.Count.Value };
entry.Value = res;
return res;
});
@ -68,14 +68,20 @@ namespace BTCPayServer.Services.Notifications
return $"notifications-{userId}";
}
public async Task<(List<NotificationViewModel> Items, int Count)> GetNotifications(NotificationsQuery query)
public async Task<(List<NotificationViewModel> Items, int? Count)> GetNotifications(NotificationsQuery query)
{
await using var dbContext = _factory.CreateContext();
var queryables = GetNotificationsQueryable(dbContext, query);
var items = (await queryables.withPaging.ToListAsync()).Select(ToViewModel).Where(model => model != null).ToList();
return (Items: (await queryables.withPaging.ToListAsync()).Select(ToViewModel).Where(model => model != null).ToList(),
Count: await queryables.withoutPaging.CountAsync());
int? count = null;
if (query.Seen is false)
{
// Unseen notifications aren't likely to be too huge, so count should be fast
count = await queryables.withoutPaging.CountAsync();
}
return (Items: items, Count: count);
}
private (IQueryable<NotificationData> withoutPaging, IQueryable<NotificationData> withPaging)

View file

@ -76,7 +76,7 @@ namespace BTCPayServer.Services.PaymentRequests
await context.SaveChangesAsync(cancellationToken);
}
public async Task<(int Total, PaymentRequestData[] Items)> FindPaymentRequests(PaymentRequestQuery query, CancellationToken cancellationToken = default)
public async Task<PaymentRequestData[]> FindPaymentRequests(PaymentRequestQuery query, CancellationToken cancellationToken = default)
{
using var context = _ContextFactory.CreateContext();
var queryable = context.PaymentRequests.Include(data => data.StoreData).AsQueryable();
@ -109,8 +109,6 @@ namespace BTCPayServer.Services.PaymentRequests
i.StoreData != null && i.StoreData.UserStores.Any(u => u.ApplicationUserId == query.UserId));
}
var total = await queryable.CountAsync(cancellationToken);
queryable = queryable.OrderByDescending(u => u.Created);
if (query.Skip.HasValue)
@ -122,7 +120,8 @@ namespace BTCPayServer.Services.PaymentRequests
{
queryable = queryable.Take(query.Count.Value);
}
return (total, await queryable.ToArrayAsync(cancellationToken));
var items = await queryable.ToArrayAsync(cancellationToken);
return items;
}
public async Task<InvoiceEntity[]> GetInvoicesForPaymentRequest(string paymentRequestId,

View file

@ -243,7 +243,7 @@
</ul>
</div>
</div>
<form class="@(Model.Total > 0 ? "col-xl-7 col-xxl-8 " : "")mb-4" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" method="get">
<form class="@(Model.Invoices.Count > 0 ? "col-xl-7 col-xxl-8 " : "")mb-4" asp-action="ListInvoices" asp-route-storeId="@Model.StoreId" method="get">
<input type="hidden" asp-for="Count" />
<input asp-for="TimezoneOffset" type="hidden" />
<div class="input-group">
@ -278,7 +278,7 @@
<span asp-validation-for="SearchTerm" class="text-danger"></span>
</form>
@if (Model.Total > 0)
@if (Model.Invoices.Count > 0)
{
<form method="post" id="MassAction" asp-action="MassAction" class="">
<div class="d-inline-flex align-items-center pb-2 float-xl-end mb-2 gap-3">

View file

@ -11,7 +11,7 @@
Settings
</a>
</div>
@if (Model.Total > 0)
@if (Model.Items.Count > 0)
{
<form method="post" asp-action="MassAction">
<div class="row button-row">
@ -34,7 +34,7 @@
<thead>
<tr>
<th width="30px" class="only-for-js">
@if (Model.Total > 0)
@if (Model.Items.Count > 0)
{
<input name="selectedItems" id="selectAllCheckbox" type="checkbox" class="form-check-input" />
}

View file

@ -49,7 +49,7 @@
<div class="row">
<div class="col-lg-12">
@if (Model.Total > 0)
@if (Model.Items.Count > 0)
{
<table class="table table-hover table-responsive-md">
<thead>