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

Support complex filters per report #5741

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
15 changes: 4 additions & 11 deletions BTCPayServer.Client/Models/StoreReportRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ namespace BTCPayServer.Client.Models;
public class StoreReportRequest
{
public string ViewName { get; set; }
public TimePeriod TimePeriod { get; set; }
public JObject Query { get; set; }

}
public class StoreReportResponse
{
Expand All @@ -32,8 +33,8 @@ public Field(string name, string type)
}
public IList<Field> Fields { get; set; } = new List<Field>();
public List<JArray> Data { get; set; }
public DateTimeOffset From { get; set; }
public DateTimeOffset To { get; set; }
// public DateTimeOffset From { get; set; }
// public DateTimeOffset To { get; set; }
public List<ChartDefinition> Charts { get; set; }

public int GetIndex(string fieldName)
Expand All @@ -52,11 +53,3 @@ public class ChartDefinition
public List<string> Aggregates { get; set; } = new List<string>();
public List<string> Filters { get; set; } = new List<string>();
}

public class TimePeriod
{
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? From { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? To { get; set; }
}
5 changes: 3 additions & 2 deletions BTCPayServer.Tests/UnitTest1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Controllers.GreenField;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Fido2;
Expand Down Expand Up @@ -2914,8 +2915,8 @@ public async Task CanCreateReports()

private async Task<StoreReportResponse> GetReport(TestAccount acc, StoreReportRequest req)
{
var controller = acc.GetController<UIReportsController>();
return (await controller.StoreReportsJson(acc.StoreId, req)).AssertType<JsonResult>()
var controller = acc.GetController<GreenfieldReportsController>();
return (await controller.StoreReports(acc.StoreId, req)).AssertType<JsonResult>()
.Value
.AssertType<StoreReportResponse>();
}
Expand Down
30 changes: 21 additions & 9 deletions BTCPayServer/Controllers/GreenField/GreenfieldReportsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using BTCPayServer.Services;
using System.Linq;
using System.Threading;
using BTCPayServer.Forms;

namespace BTCPayServer.Controllers.GreenField;

Expand All @@ -22,10 +23,13 @@ namespace BTCPayServer.Controllers.GreenField;
[EnableCors(CorsPolicies.All)]
public class GreenfieldReportsController : Controller
{
private readonly FormDataService _formDataService;

public GreenfieldReportsController(
ApplicationDbContextFactory dbContextFactory,
ReportService reportService)
ReportService reportService, FormDataService formDataService)
{
_formDataService = formDataService;
DBContextFactory = dbContextFactory;
ReportService = reportService;
}
Expand All @@ -39,26 +43,34 @@ public async Task<IActionResult> StoreReports(string storeId, [FromBody] StoreRe
{
vm ??= new StoreReportRequest();
vm.ViewName ??= "Payments";
vm.TimePeriod ??= new TimePeriod();
vm.TimePeriod.To ??= DateTime.UtcNow;
vm.TimePeriod.From ??= vm.TimePeriod.To.Value.AddMonths(-1);
var from = vm.TimePeriod.From.Value;
var to = vm.TimePeriod.To.Value;


// vm.TimePeriod ??= new TimePeriod();
// vm.TimePeriod.To ??= DateTime.UtcNow;
// vm.TimePeriod.From ??= vm.TimePeriod.To.Value.AddMonths(-1);
// var from = vm.TimePeriod.From.Value;
// var to = vm.TimePeriod.To.Value;

if (ReportService.ReportProviders.TryGetValue(vm.ViewName, out var report))
{
if (!report.IsAvailable())
return this.CreateAPIError(503, "view-unavailable", $"This view is unavailable at this moment");

var ctx = new Services.Reporting.QueryContext(storeId, from, to);

var form = report.GetForm();
_formDataService.SetValues(form, vm.Query);
if (!_formDataService.Validate(form, ModelState))
{
return this.CreateValidationError(ModelState);
}

var ctx = new Services.Reporting.QueryContext(storeId, _formDataService.GetValues(form));
await report.Query(ctx, cancellationToken);
var result = new StoreReportResponse
{
Fields = ctx.ViewDefinition?.Fields ?? new List<StoreReportResponse.Field>(),
Charts = ctx.ViewDefinition?.Charts ?? new List<ChartDefinition>(),
Data = ctx.Data.Select(d => new JArray(d)).ToList(),
From = from,
To = to
};
return Json(result);
}
Expand Down
51 changes: 44 additions & 7 deletions BTCPayServer/Controllers/UIReportsController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
Expand All @@ -14,6 +15,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
using System.Threading;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Forms;
using Newtonsoft.Json.Linq;

namespace BTCPayServer.Controllers;
Expand All @@ -22,15 +25,19 @@ namespace BTCPayServer.Controllers;
[AutoValidateAntiforgeryToken]
public partial class UIReportsController : Controller
{
private readonly FormDataService _formDataService;

public UIReportsController(
ApplicationDbContextFactory dbContextFactory,
GreenfieldReportsController api,
ReportService reportService,
DisplayFormatter displayFormatter,
BTCPayServerEnvironment env,
BTCPayNetworkProvider networkProvider,
TransactionLinkProviders transactionLinkProviders)
TransactionLinkProviders transactionLinkProviders,
FormDataService formDataService)
{
_formDataService = formDataService;
Api = api;
ReportService = reportService;
Env = env;
Expand All @@ -47,13 +54,41 @@ public partial class UIReportsController : Controller
public ApplicationDbContextFactory DBContextFactory { get; }
public TransactionLinkProviders TransactionLinkProviders { get; }

[HttpPost("stores/{storeId}/reports")]
[HttpPost("stores/{storeId}/reports/{viewName}")]
[AcceptMediaTypeConstraint("application/json")]
[Authorize(Policy = Policies.CanViewReports, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> StoreReportsJson(string storeId, [FromBody] StoreReportRequest? request = null, bool fakeData = false, CancellationToken cancellation = default)
public async Task<IActionResult> StoreReportsJson(string storeId, string viewName, bool fakeData = false, CancellationToken cancellation = default)
{
var result = await Api.StoreReports(storeId, request, cancellation);



var form = ReportService.ReportProviders[viewName].GetForm();

if (Request.HasFormContentType)
{
form.ApplyValuesFromForm(Request.Form);

}
// if (!_formDataService.Validate(form, ModelState))
// {
// if(Request.Headers["Accept"].ToString()?.StartsWith("application/json", StringComparison.InvariantCultureIgnoreCase) is true)
// {
// return this.CreateValidationError(ModelState);
// }
//
// return StoreReports(storeId, viewName);
//
//
// }

var query = _formDataService.GetValues(form);

var result = await Api.StoreReports(storeId, new StoreReportRequest()
{
ViewName = viewName,
Query = query
} , cancellation);
if (fakeData && Env.CheatMode)
{
var r = (StoreReportResponse)((JsonResult)result!).Value!;
Expand All @@ -62,18 +97,20 @@ public async Task<IActionResult> StoreReportsJson(string storeId, [FromBody] Sto
return result;
}

[HttpGet("stores/{storeId}/reports")]
[HttpGet("stores/{storeId}/reports/{viewName?}")]
[AcceptMediaTypeConstraint("text/html")]
[Authorize(Policy = Policies.CanViewReports, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult StoreReports(
string storeId,
string ? viewName = null)
string viewName)
{
if(viewName is null)
return RedirectToAction(nameof(StoreReports), new { storeId, viewName = "Payments" });
var vm = new StoreReportsViewModel
{
InvoiceTemplateUrl = Url.Action(nameof(UIInvoiceController.Invoice), "UIInvoice", new { invoiceId = "INVOICE_ID" }),
ExplorerTemplateUrls = TransactionLinkProviders.ToDictionary(p => p.Key.CryptoCode, p => p.Value.BlockExplorerLink?.Replace("{0}", "TX_ID")),
Request = new StoreReportRequest { ViewName = viewName ?? "Payments" },
// Request = new StoreReportRequest { ViewName = viewName ?? "Payments" },
AvailableViews = ReportService.ReportProviders
.Values
.Where(r => r.IsAvailable())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public class StoreReportsViewModel
{
public string InvoiceTemplateUrl { get; set; }
public Dictionary<string,string> ExplorerTemplateUrls { get; set; }
public StoreReportRequest Request { get; set; }
// public StoreReportRequest Request { get; set; }
public List<string> AvailableViews { get; set; }
public StoreReportResponse Result { get; set; }
}
12 changes: 10 additions & 2 deletions BTCPayServer/Services/Reporting/OnChainWalletReportProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Services.Stores;
using Dapper;
using NBitcoin;
Expand Down Expand Up @@ -67,7 +68,14 @@ public override async Task Query(QueryContext queryContext, CancellationToken ca
var store = await StoreRepository.FindStore(queryContext.StoreId);
if (store is null)
return;
var interval = DateTimeOffset.UtcNow - queryContext.From;
var dates = GetFromTo(queryContext.Query);
TimeSpan interval = TimeSpan.FromDays(30);
if (dates.From is not null)
{
interval = DateTimeOffset.UtcNow - dates.From.Value;
}


foreach (var settings in store.GetDerivationSchemeSettings(NetworkProvider))
{
var walletId = new WalletId(store.Id, settings.Network.CryptoCode);
Expand All @@ -90,7 +98,7 @@ public override async Task Query(QueryContext queryContext, CancellationToken ca
foreach (var r in rows)
{
var date = (DateTimeOffset)r.seen_at;
if (date > queryContext.To)
if (dates.To is not null && date > dates.To.Value)
continue;
var values = queryContext.AddData();
var balanceChange = Money.Satoshis((long)r.balance_change).ToDecimal(MoneyUnit.BTC);
Expand Down
5 changes: 3 additions & 2 deletions BTCPayServer/Services/Reporting/PaymentsReportProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ public override async Task Query(QueryContext queryContext, CancellationToken ca
{
queryContext.ViewDefinition = CreateViewDefinition();
await using var ctx = DbContextFactory.CreateContext();
var dates = GetFromTo(queryContext.Query);
var conn = ctx.Database.GetDbConnection();
string[] fields =
{
Expand All @@ -110,8 +111,8 @@ public override async Task Query(QueryContext queryContext, CancellationToken ca
parameters: new
{
storeId = queryContext.StoreId,
from = queryContext.From,
to = queryContext.To
from = dates.To?? DateTimeOffset.MinValue,
to = dates.To?? DateTimeOffset.MaxValue
},
cancellationToken: cancellation);
var rows = await conn.QueryAsync(command);
Expand Down
7 changes: 4 additions & 3 deletions BTCPayServer/Services/Reporting/PayoutsReportProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
Expand All @@ -24,16 +25,16 @@ public class PayoutsReportProvider : ReportProvider
_pullPaymentHostedService = pullPaymentHostedService;
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
}

public override string Name => "Payouts";
public override async Task Query(QueryContext queryContext, CancellationToken cancellation)
{
queryContext.ViewDefinition = CreateDefinition();
foreach (var payout in (await _pullPaymentHostedService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
{
Stores = new[] {queryContext.StoreId},
From = queryContext.From,
To = queryContext.To,
// From = queryContext.From,
// To = queryContext.To,
IncludeArchived = true,
IncludePullPaymentData = true,

Expand Down
5 changes: 3 additions & 2 deletions BTCPayServer/Services/Reporting/ProductsReportProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ public override async Task Query(QueryContext queryContext, CancellationToken ca
var appsById = (await Apps.GetApps(queryContext.StoreId)).ToDictionary(o => o.Id);
var tagAllinvoicesApps = appsById.Values.Where(a => a.TagAllInvoices).ToList();
queryContext.ViewDefinition = CreateDefinition();
var dates = GetFromTo(queryContext.Query);
foreach (var i in (await InvoiceRepository.GetInvoices(new InvoiceQuery
{
IncludeArchived = true,
IncludeAddresses = false,
IncludeEvents = false,
IncludeRefunds = false,
StartDate = queryContext.From,
EndDate = queryContext.To,
StartDate = dates.From,
EndDate = dates.To,
StoreId = new[] { queryContext.StoreId }
}, cancellation)).OrderBy(c => c.InvoiceTime))
{
Expand Down
9 changes: 4 additions & 5 deletions BTCPayServer/Services/Reporting/QueryContext.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
#nullable enable
using System;
using System.Collections.Generic;
using Newtonsoft.Json.Linq;

namespace BTCPayServer.Services.Reporting
{
public record QueryContext
{
public QueryContext(string storeId, DateTimeOffset from, DateTimeOffset to)
public QueryContext(string storeId, JObject query)
{
StoreId = storeId;
From = from;
To = to;
Query = query;
}
public string StoreId { get; }
public DateTimeOffset From { get; }
public DateTimeOffset To { get; }
public JObject Query { get; }
public ViewDefinition? ViewDefinition { get; set; }

public IList<object?> AddData()
Expand Down