Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve invoice filtering UI #4914

Merged
merged 7 commits into from May 19, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 45 additions & 4 deletions BTCPayServer.Tests/FastTests.cs
Expand Up @@ -1042,14 +1042,13 @@ public void CanUsePermission()
[Fact]
public void CanParseFilter()
{
var storeId = "6DehZnc9S7qC6TUTNWuzJ1pFsHTHvES6An21r3MjvLey";
var filter = "storeid:abc, status:abed, blabhbalh ";
var search = new SearchString(filter);
Assert.Equal("storeid:abc, status:abed, blabhbalh", search.ToString());
Assert.Equal("blabhbalh", search.TextSearch);
Assert.Single(search.Filters["storeid"]);
Assert.Single(search.Filters["status"]);
Assert.Equal("abc", search.Filters["storeid"].First());
Assert.Equal("abed", search.Filters["status"].First());
Assert.Single(search.Filters["storeid"], "abc");
Assert.Single(search.Filters["status"], "abed");

filter = "status:abed, status:abed2";
search = new SearchString(filter);
Expand All @@ -1064,6 +1063,48 @@ public void CanParseFilter()
search = new SearchString(filter);
Assert.Equal("2019-04-25 01:00 AM", search.Filters["startdate"].First());
Assert.Equal("hekki", search.TextSearch);

// modify search
filter = $"status:settled,exceptionstatus:paidLate,unusual:true, fulltext searchterm, storeid:{storeId},startdate:2019-04-25 01:00:00";
search = new SearchString(filter);
Assert.Equal(filter, search.ToString());
Assert.Equal("fulltext searchterm", search.TextSearch);
Assert.Single(search.Filters["storeid"], storeId);
Assert.Single(search.Filters["status"], "settled");
Assert.Single(search.Filters["exceptionstatus"], "paidLate");
Assert.Single(search.Filters["unusual"], "true");

// toggle off bool with same value
var modified = new SearchString(search.Toggle("unusual", "true"));
Assert.Null(modified.GetFilterBool("unusual"));

// add to array
modified = new SearchString(modified.Toggle("status", "processing"));
var statusArray = modified.GetFilterArray("status");
Assert.Equal(2, statusArray.Length);
Assert.Contains("processing", statusArray);
Assert.Contains("settled", statusArray);

// toggle off array with same value
modified = new SearchString(modified.Toggle("status", "settled"));
statusArray = modified.GetFilterArray("status");
Assert.Single(statusArray, "processing");

// toggle off array with null value
modified = new SearchString(modified.Toggle("status", null));
Assert.Null(modified.GetFilterArray("status"));

// toggle off date with null value
modified = new SearchString(modified.Toggle("startdate", "-7d"));
Assert.Single(modified.GetFilterArray("startdate"), "-7d");
modified = new SearchString(modified.Toggle("startdate", null));
Assert.Null(modified.GetFilterArray("startdate"));

// toggle off date with same value
modified = new SearchString(modified.Toggle("enddate", "-7d"));
Assert.Single(modified.GetFilterArray("enddate"), "-7d");
modified = new SearchString(modified.Toggle("enddate", "-7d"));
Assert.Null(modified.GetFilterArray("enddate"));
}

[Fact]
Expand Down
4 changes: 2 additions & 2 deletions BTCPayServer.Tests/SeleniumTests.cs
Expand Up @@ -720,8 +720,8 @@ public async Task CanCreateStores()
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);

// unarchive via list
s.Driver.FindElement(By.Id("SearchOptionsToggle")).Click();
s.Driver.FindElement(By.Id("SearchOptionsIncludeArchived")).Click();
s.Driver.FindElement(By.Id("StatusOptionsToggle")).Click();
s.Driver.FindElement(By.Id("StatusOptionsIncludeArchived")).Click();
Assert.Contains(invoiceId, s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector($".selector[value=\"{invoiceId}\"]")).Click();
s.Driver.FindElement(By.Id("ActionsDropdownToggle")).Click();
Expand Down
54 changes: 31 additions & 23 deletions BTCPayServer/Controllers/UIInvoiceController.UI.cs
Expand Up @@ -1073,34 +1073,44 @@ public async Task<IActionResult> UpdateCustomer(string invoiceId, [FromBody] Upd
public async Task<IActionResult> ListInvoices(InvoicesModel? model = null)
{
model = this.ParseListQuery(model ?? new InvoicesModel());
var fs = new SearchString(model.SearchTerm);
var timezoneOffset = model.TimezoneOffset ?? 0;
var searchTerm = string.IsNullOrEmpty(model.SearchText) ? model.SearchTerm : $"{model.SearchText},{model.SearchTerm}";
var fs = new SearchString(searchTerm, timezoneOffset);
string? storeId = model.StoreId;
var storeIds = new HashSet<string>();
if (fs.GetFilterArray("storeid") is string[] l)
{
foreach (var i in l)
storeIds.Add(i);
}
if (storeId is not null)
{
storeIds.Add(storeId);
model.StoreId = storeId;
}
model.StoreIds = storeIds.ToArray();

InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
invoiceQuery.StoreId = model.StoreIds;
if (fs.GetFilterArray("storeid") is { } l)
{
foreach (var i in l)
storeIds.Add(i);
}
model.Search = fs;
model.SearchText = fs.TextSearch;

InvoiceQuery invoiceQuery = GetInvoiceQuery(fs, timezoneOffset);
invoiceQuery.StoreId = storeIds.ToArray();
invoiceQuery.Take = model.Count;
invoiceQuery.Skip = model.Skip;
invoiceQuery.IncludeRefunds = true;
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);

model.IncludeArchived = invoiceQuery.IncludeArchived;
// Apps
var apps = await _appService.GetAllApps(GetUserId(), false, storeId);
model.Apps = apps.Select(a => new InvoiceAppModel
{
Id = a.Id,
AppName = a.AppName,
AppType = a.AppType,
AppOrderId = AppService.GetAppOrderId(a.AppType, a.Id)
}).ToList();

foreach (var invoice in list)
{
var state = invoice.GetInvoiceState();
model.Invoices.Add(new InvoiceModel()
model.Invoices.Add(new InvoiceModel
{
Status = state,
ShowCheckout = invoice.Status == InvoiceStatusLegacy.New,
Expand All @@ -1119,10 +1129,9 @@ public async Task<IActionResult> ListInvoices(InvoicesModel? model = null)
return View(model);
}

private InvoiceQuery GetInvoiceQuery(string? searchTerm = null, int timezoneOffset = 0)
private InvoiceQuery GetInvoiceQuery(SearchString fs, int timezoneOffset = 0)
{
var fs = new SearchString(searchTerm);
var invoiceQuery = new InvoiceQuery()
return new InvoiceQuery
{
TextSearch = fs.TextSearch,
UserId = GetUserId(),
Expand All @@ -1136,7 +1145,6 @@ private InvoiceQuery GetInvoiceQuery(string? searchTerm = null, int timezoneOffs
StartDate = fs.GetFilterDate("startdate", timezoneOffset),
EndDate = fs.GetFilterDate("enddate", timezoneOffset)
};
return invoiceQuery;
}

[HttpGet]
Expand All @@ -1147,17 +1155,17 @@ public async Task<IActionResult> Export(string format, string? storeId = null, s
var model = new InvoiceExport(_CurrencyNameTable);
var fs = new SearchString(searchTerm);
var storeIds = new HashSet<string>();
if (fs.GetFilterArray("storeid") is string[] l)
{
foreach (var i in l)
storeIds.Add(i);
}
if (storeId is not null)
{
storeIds.Add(storeId);
}
if (fs.GetFilterArray("storeid") is { } l)
{
foreach (var i in l)
storeIds.Add(i);
}

InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset);
InvoiceQuery invoiceQuery = GetInvoiceQuery(fs, timezoneOffset);
invoiceQuery.StoreId = storeIds.ToArray();
invoiceQuery.Skip = 0;
invoiceQuery.Take = int.MaxValue;
Expand Down
3 changes: 3 additions & 0 deletions BTCPayServer/Controllers/UIInvoiceController.cs
Expand Up @@ -58,6 +58,7 @@ public partial class UIInvoiceController : Controller
private readonly InvoiceActivator _invoiceActivator;
private readonly LinkGenerator _linkGenerator;
private readonly IAuthorizationService _authorizationService;
private readonly AppService _appService;

public WebhookSender WebhookNotificationManager { get; }

Expand All @@ -81,6 +82,7 @@ public partial class UIInvoiceController : Controller
UIWalletsController walletsController,
InvoiceActivator invoiceActivator,
LinkGenerator linkGenerator,
AppService appService,
IAuthorizationService authorizationService)
{
_displayFormatter = displayFormatter;
Expand All @@ -102,6 +104,7 @@ public partial class UIInvoiceController : Controller
_invoiceActivator = invoiceActivator;
_linkGenerator = linkGenerator;
_authorizationService = authorizationService;
_appService = appService;
}


Expand Down
2 changes: 1 addition & 1 deletion BTCPayServer/Controllers/UIPaymentRequestController.cs
Expand Up @@ -78,7 +78,7 @@ public async Task<IActionResult> GetPaymentRequests(string storeId, ListPaymentR
model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel());

var store = GetCurrentStore();
var includeArchived = new SearchString(model.SearchTerm).GetFilterBool("includearchived") == true;
var includeArchived = new SearchString(model.SearchTerm, model.TimezoneOffset ?? 0).GetFilterBool("includearchived") == true;
var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery
{
UserId = GetUserId(),
Expand Down
17 changes: 13 additions & 4 deletions BTCPayServer/Models/InvoicingModels/InvoicesModel.cs
@@ -1,17 +1,18 @@
using System;
using System.Collections.Generic;
using BTCPayServer.Client.Models;
using BTCPayServer.Services.Invoices;

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

public string SearchText { get; set; }
public SearchString Search { get; set; }
public List<InvoiceAppModel> Apps { get; set; }
}

public class InvoiceModel
Expand All @@ -34,4 +35,12 @@ public class InvoiceModel
public InvoiceDetailsModel Details { get; set; }
public bool HasRefund { get; set; }
}

public class InvoiceAppModel
{
public string Id { get; set; }
public string AppName { get; set; }
public string AppType { get; set; }
public string AppOrderId { get; set; }
}
}