Skip to content

Commit

Permalink
feat: show filtered transaction list on app connection page (#809)
Browse files Browse the repository at this point in the history
* feat: show filtered transaction list on app connection page

* fix: tests
  • Loading branch information
rolznz authored Nov 27, 2024
1 parent 0063bf0 commit 8addcd4
Show file tree
Hide file tree
Showing 12 changed files with 74 additions and 28 deletions.
2 changes: 1 addition & 1 deletion api/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type API interface {
SignMessage(ctx context.Context, message string) (*SignMessageResponse, error)
RedeemOnchainFunds(ctx context.Context, toAddress string, amount uint64, sendAll bool) (*RedeemOnchainFundsResponse, error)
GetBalances(ctx context.Context) (*BalancesResponse, error)
ListTransactions(ctx context.Context, limit uint64, offset uint64) (*ListTransactionsResponse, error)
ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64) (*ListTransactionsResponse, error)
SendPayment(ctx context.Context, invoice string) (*SendPaymentResponse, error)
CreateInvoice(ctx context.Context, amount uint64, description string) (*MakeInvoiceResponse, error)
LookupInvoice(ctx context.Context, paymentHash string) (*LookupInvoiceResponse, error)
Expand Down
4 changes: 2 additions & 2 deletions api/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ func (api *api) LookupInvoice(ctx context.Context, paymentHash string) (*LookupI
}

// TODO: accept offset, limit params for pagination
func (api *api) ListTransactions(ctx context.Context, limit uint64, offset uint64) (*ListTransactionsResponse, error) {
func (api *api) ListTransactions(ctx context.Context, appId *uint, limit uint64, offset uint64) (*ListTransactionsResponse, error) {
if api.svc.GetLNClient() == nil {
return nil, errors.New("LNClient not started")
}
transactions, err := api.svc.GetTransactionsService().ListTransactions(ctx, 0, 0, limit, offset, true, false, nil, api.svc.GetLNClient(), nil)
transactions, err := api.svc.GetTransactionsService().ListTransactions(ctx, 0, 0, limit, offset, true, false, nil, api.svc.GetLNClient(), appId, true)
if err != nil {
return nil, err
}
Expand Down
10 changes: 7 additions & 3 deletions frontend/src/components/EmptyState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ interface Props {
description: string;
buttonText: string;
buttonLink: string;
showButton?: boolean;
}

const EmptyState: React.FC<Props> = ({
Expand All @@ -17,16 +18,19 @@ const EmptyState: React.FC<Props> = ({
description: subMessage,
buttonText,
buttonLink,
showButton = true,
}) => {
return (
<div className="flex flex-1 items-center justify-center rounded-lg border border-dashed shadow-sm p-8">
<div className="flex flex-col items-center gap-1 text-center max-w-sm">
<Icon className="w-10 h-10 text-muted-foreground" />
<h3 className="mt-4 text-lg font-semibold">{message}</h3>
<p className="text-sm text-muted-foreground">{subMessage}</p>
<Link to={buttonLink}>
<Button className="mt-4">{buttonText}</Button>
</Link>
{showButton && (
<Link to={buttonLink}>
<Button className="mt-4">{buttonText}</Button>
</Link>
)}
</div>
</div>
);
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/components/TransactionsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@ import Loading from "src/components/Loading";
import TransactionItem from "src/components/TransactionItem";
import { useTransactions } from "src/hooks/useTransactions";

function TransactionsList() {
const { data: transactions, isLoading } = useTransactions();
type TransactionsListProps = {
appId?: number;
showReceiveButton?: boolean;
};

function TransactionsList({
appId,
showReceiveButton = true,
}: TransactionsListProps) {
const { data: transactions, isLoading } = useTransactions(appId);

if (isLoading) {
return <Loading />;
Expand All @@ -20,6 +28,7 @@ function TransactionsList() {
description="Your most recent incoming and outgoing payments will show up here."
buttonText="Receive Your First Payment"
buttonLink="/wallet/receive"
showButton={showReceiveButton}
/>
) : (
<>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/hooks/useOnboardingData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const useOnboardingData = (): UseOnboardingDataResponse => {
const { data: channels } = useChannels();
const { data: info, hasChannelManagement, hasMnemonic } = useInfo();
const { data: nodeConnectionInfo } = useNodeConnectionInfo();
const { data: transactions } = useTransactions(false, 1);
const { data: transactions } = useTransactions(undefined, false, 1);

const isLoading =
!apps ||
Expand Down
9 changes: 7 additions & 2 deletions frontend/src/hooks/useTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ const pollConfiguration: SWRConfiguration = {
refreshInterval: 3000,
};

export function useTransactions(poll = false, limit = 100, page = 1) {
export function useTransactions(
appId?: number,
poll = false,
limit = 100,
page = 1
) {
const offset = (page - 1) * limit;
return useSWR<Transaction[]>(
`/api/transactions?limit=${limit}&offset=${offset}`,
`/api/transactions?limit=${limit}&offset=${offset}&appId=${appId}`,
swrFetcher,
poll ? pollConfiguration : undefined
);
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/screens/apps/ShowApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import AppHeader from "src/components/AppHeader";
import { IsolatedAppTopupDialog } from "src/components/IsolatedAppTopupDialog";
import Loading from "src/components/Loading";
import Permissions from "src/components/Permissions";
import TransactionsList from "src/components/TransactionsList";
import {
AlertDialog,
AlertDialogAction,
Expand Down Expand Up @@ -222,7 +223,7 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
/>
<Card>
<CardHeader>
<CardTitle>Info</CardTitle>
<CardTitle>Connection Summary</CardTitle>
</CardHeader>
<CardContent className="slashed-zero">
<Table>
Expand Down Expand Up @@ -363,6 +364,19 @@ function AppInternal({ app, refetchApp, capabilities }: AppInternalProps) {
/>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>
<div className="flex flex-row justify-between items-center">
Transactions
</div>
</CardTitle>
</CardHeader>
<CardContent>
<TransactionsList appId={app.id} showReceiveButton={false} />
</CardContent>
</Card>
</div>
</div>
</>
Expand Down
10 changes: 9 additions & 1 deletion http/http_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ func (httpSvc *HttpService) listTransactionsHandler(c echo.Context) error {

limit := uint64(20)
offset := uint64(0)
var appId *uint

if limitParam := c.QueryParam("limit"); limitParam != "" {
if parsedLimit, err := strconv.ParseUint(limitParam, 10, 64); err == nil {
Expand All @@ -500,7 +501,14 @@ func (httpSvc *HttpService) listTransactionsHandler(c echo.Context) error {
}
}

transactions, err := httpSvc.api.ListTransactions(ctx, limit, offset)
if appIdParam := c.QueryParam("appId"); appIdParam != "" {
if parsedAppId, err := strconv.ParseUint(appIdParam, 10, 64); err == nil {
var unsignedAppId = uint(parsedAppId)
appId = &unsignedAppId
}
}

transactions, err := httpSvc.api.ListTransactions(ctx, appId, limit, offset)

if err != nil {
return c.JSON(http.StatusInternalServerError, ErrorResponse{
Expand Down
2 changes: 1 addition & 1 deletion nip47/controllers/list_transactions_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (controller *nip47Controller) HandleListTransactionsEvent(ctx context.Conte
}

// TODO: listParams.Unpaid needs to be updated to support ability to fetch only unpaid outgoing transactions
dbTransactions, err := controller.transactionsService.ListTransactions(ctx, listParams.From, listParams.Until, limit, listParams.Offset, listParams.Unpaid, listParams.Unpaid, transactionType, controller.lnClient, &appId)
dbTransactions, err := controller.transactionsService.ListTransactions(ctx, listParams.From, listParams.Until, limit, listParams.Offset, listParams.Unpaid, listParams.Unpaid, transactionType, controller.lnClient, &appId, false)
if err != nil {
logger.Logger.WithFields(logrus.Fields{
"params": listParams,
Expand Down
18 changes: 9 additions & 9 deletions transactions/list_transactions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestListTransactions_Paid(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, false, nil, svc.LNClient, nil)
incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, false, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(incomingTransactions))
assert.Equal(t, uint64(123000), incomingTransactions[0].AmountMsat)
Expand Down Expand Up @@ -112,7 +112,7 @@ func TestListTransactions_UnpaidIncoming(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, true, nil, svc.LNClient, nil)
incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, false, true, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 3, len(incomingTransactions))
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, incomingTransactions[0].State)
Expand Down Expand Up @@ -179,7 +179,7 @@ func TestListTransactions_UnpaidOutgoing(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

outgoingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, false, nil, svc.LNClient, nil)
outgoingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, false, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 3, len(outgoingTransactions))
assert.Equal(t, constants.TRANSACTION_STATE_SETTLED, outgoingTransactions[0].State)
Expand Down Expand Up @@ -246,7 +246,7 @@ func TestListTransactions_Unpaid(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

outgoingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, true, nil, svc.LNClient, nil)
outgoingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 0, 0, true, true, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 5, len(outgoingTransactions))
}
Expand Down Expand Up @@ -281,7 +281,7 @@ func TestListTransactions_Limit(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 0, false, false, nil, svc.LNClient, nil)
incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 0, false, false, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(incomingTransactions))
assert.Equal(t, "first", incomingTransactions[0].Description)
Expand Down Expand Up @@ -337,7 +337,7 @@ func TestListTransactions_Offset(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 2, false, false, nil, svc.LNClient, nil)
incomingTransactions, err := transactionsService.ListTransactions(ctx, 0, 0, 1, 2, false, false, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(incomingTransactions))
assert.Equal(t, "third", incomingTransactions[0].Description)
Expand Down Expand Up @@ -387,7 +387,7 @@ func TestListTransactions_FromUntil(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

incomingTransactions, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, false, nil, svc.LNClient, nil)
incomingTransactions, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, false, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(incomingTransactions))
assert.Equal(t, "second", incomingTransactions[0].Description)
Expand Down Expand Up @@ -448,7 +448,7 @@ func TestListTransactions_FromUntilUnpaidOutgoing(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

incomingTransactions, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, true, false, nil, svc.LNClient, nil)
incomingTransactions, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, true, false, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(incomingTransactions))
assert.Equal(t, "second", incomingTransactions[0].Description)
Expand Down Expand Up @@ -510,7 +510,7 @@ func TestListTransactions_FromUntilUnpaidIncoming(t *testing.T) {

transactionsService := NewTransactionsService(svc.DB, svc.EventPublisher)

incomingTransactions, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, true, nil, svc.LNClient, nil)
incomingTransactions, err := transactionsService.ListTransactions(ctx, uint64(time.Now().Add(4*time.Minute).Unix()), uint64(time.Now().Add(6*time.Minute).Unix()), 0, 0, false, true, nil, svc.LNClient, nil, false)
assert.NoError(t, err)
assert.Equal(t, 1, len(incomingTransactions))
assert.Equal(t, "second", incomingTransactions[0].Description)
Expand Down
6 changes: 3 additions & 3 deletions transactions/transactions_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type TransactionsService interface {
events.EventSubscriber
MakeInvoice(ctx context.Context, amount uint64, description string, descriptionHash string, expiry uint64, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
LookupTransaction(ctx context.Context, paymentHash string, transactionType *string, lnClient lnclient.LNClient, appId *uint) (*Transaction, error)
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint) (transactions []Transaction, err error)
ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool) (transactions []Transaction, err error)
SendPaymentSync(ctx context.Context, payReq string, metadata map[string]interface{}, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
SendKeysend(ctx context.Context, amount uint64, destination string, customRecords []lnclient.TLVRecord, preimage string, lnClient lnclient.LNClient, appId *uint, requestEventId *uint) (*Transaction, error)
}
Expand Down Expand Up @@ -510,7 +510,7 @@ func (svc *transactionsService) LookupTransaction(ctx context.Context, paymentHa
return &transaction, nil
}

func (svc *transactionsService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint) (transactions []Transaction, err error) {
func (svc *transactionsService) ListTransactions(ctx context.Context, from, until, limit, offset uint64, unpaidOutgoing bool, unpaidIncoming bool, transactionType *string, lnClient lnclient.LNClient, appId *uint, forceFilterByAppId bool) (transactions []Transaction, err error) {
svc.checkUnsettledTransactions(ctx, lnClient)

tx := svc.db
Expand Down Expand Up @@ -544,7 +544,7 @@ func (svc *transactionsService) ListTransactions(ctx context.Context, from, unti
if result.RowsAffected == 0 {
return nil, NewNotFoundError()
}
if app.Isolated {
if app.Isolated || forceFilterByAppId {
tx = tx.Where("app_id == ?", *appId)
}
}
Expand Down
10 changes: 8 additions & 2 deletions wails/wails_handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,9 +246,10 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
case listTransactionsRegex.MatchString(route):
limit := uint64(20)
offset := uint64(0)
var appId *uint

// Extract limit and offset parameters
paramRegex := regexp.MustCompile(`[?&](limit|offset)=([^&]+)`)
paramRegex := regexp.MustCompile(`[?&](limit|offset|appId)=([^&]+)`)
paramMatches := paramRegex.FindAllStringSubmatch(route, -1)
for _, match := range paramMatches {
switch match[1] {
Expand All @@ -260,10 +261,15 @@ func (app *WailsApp) WailsRequestRouter(route string, method string, body string
if parsedOffset, err := strconv.ParseUint(match[2], 10, 64); err == nil {
offset = parsedOffset
}
case "appId":
if parsedAppId, err := strconv.ParseUint(match[2], 10, 64); err == nil {
var unsignedAppId = uint(parsedAppId)
appId = &unsignedAppId
}
}
}

transactions, err := app.api.ListTransactions(ctx, limit, offset)
transactions, err := app.api.ListTransactions(ctx, appId, limit, offset)
if err != nil {
return WailsRequestRouterResponse{Body: nil, Error: err.Error()}
}
Expand Down

0 comments on commit 8addcd4

Please sign in to comment.