Skip to content

Commit

Permalink
Fetch data for bookkeeper landing
Browse files Browse the repository at this point in the history
Add info to satsflow tooltip
  • Loading branch information
evansmj committed Dec 23, 2024
1 parent 41bde55 commit 4d2b599
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,17 +34,7 @@ const BalanceSheetRoot = () => {

const fetchBalanceSheetData = useCallback(
async (timeGranularity: TimeGranularity, hideZeroActivityPeriods: Boolean, startDate?: Date, endDate?: Date) => {
let startTimestamp = 1;
let endTimestamp = Math.floor(new Date().getTime());

if (startDate != null) {
startTimestamp = Math.floor(startDate.getTime() / 1000);
}
if (endDate != null) {
endTimestamp = Math.floor(endDate.getTime() / 1000);
}

getBalanceSheet(timeGranularity, hideZeroActivityPeriods, startTimestamp, endTimestamp)
getBalanceSheet(timeGranularity, hideZeroActivityPeriods, startDate, endDate)
.then((response: BalanceSheet) => {
setBalanceSheetData(response);
})
Expand Down
71 changes: 53 additions & 18 deletions apps/frontend/src/components/bookkeeper/BkprRoot/BkprRoot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,46 @@ import { SatsFlowSVG } from '../../../svgs/SatsFlow';
import { VolumeChartSVG } from '../../../svgs/VolumeChart';
import SatsFlowInfo from './SatsFlowInfo';
import VolumeInfo from './VolumeInfo';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import useHttp from '../../../hooks/use-http';
import { AppContext } from '../../../store/AppContext';
import { BookkeeperLandingData } from '../../../types/lightning-bookkeeper-landing.type';
import { BALANCE_FORMAT } from '../../../utilities/constants';
import { format } from 'd3';

function Bookkeeper() {
const Bookkeeper = () => {
const appCtx = useContext(AppContext);
const navigate = useNavigate();
const [bookkeeperLandingData, setBookkeeperLandingData] = useState<BookkeeperLandingData>();
const { getBookkeeperLanding } = useHttp();
const formatBalance = useMemo(() => format(BALANCE_FORMAT), []);

const fetchBookkeeperLandingData = useCallback(
async () => {
getBookkeeperLanding()
.then((response: BookkeeperLandingData) => {
setBookkeeperLandingData(response);
})
.catch(err => {
console.error("fetchBookkeeperLandingData error" + err);
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);

useEffect(() => {
if (appCtx.authStatus.isAuthenticated) {
fetchBookkeeperLandingData();
}
}, [appCtx.authStatus.isAuthenticated, fetchBookkeeperLandingData]);

return (
<div data-testid="bookkeeper-container">
<div className="fs-4 p-0 ps-3 fw-bold text-dark">Bookkeeper Dashboard</div>
<Container fluid className="">
<Row className="">
<Col md={5} xs={12} className="">
<Col lg={5} md={12} xs={12} className="">
<Card className="mt-4 pt-4 px-4 inner-box-shadow">
<div className="d-flex align-items-start">
<BalanceSheetSVG className="me-3" />
Expand All @@ -31,15 +62,15 @@ function Bookkeeper() {
<Card.Body className="px-0 pt-0">
Track channel and wallet balances over time.
<div className="mt-5 d-flex flex-column align-items-start">
<span className="fs-1 fw-bold text-primary">4</span>
<span className="fs-1 fw-bold text-primary">{bookkeeperLandingData?.balanceSheetSummary.numberOfChannels}</span>
<span className="fs-6 text-dark">Total Number of Channels</span>
</div>
<div className="mt-3 d-flex flex-column align-items-start">
<span className="fs-1 fw-bold text-primary">4,000,000</span>
<span className="fs-1 fw-bold text-primary">{formatBalance(bookkeeperLandingData?.balanceSheetSummary.balanceInChannels || 0)}</span>
<span className="fs-6 text-dark">Total Balance in Channels</span>
</div>
<div className="mt-3 d-flex flex-column align-items-start">
<span className="fs-1 fw-bold text-primary">4</span>
<span className="fs-1 fw-bold text-primary">{formatBalance(bookkeeperLandingData?.balanceSheetSummary.balanceInWallet || 0)}</span>
<span className="fs-6 text-dark">Total Balance in Wallet</span>
</div>
</Card.Body>
Expand All @@ -58,7 +89,7 @@ function Bookkeeper() {
</div>
</Card>
</Col>
<Col md={6} xs={12} className="ms-md-4 ms-xs-0 px-4 mt-4 d-flex flex-column">
<Col lg={6} md={12} xs={12} className="ms-lg-4 ms-xs-0 px-4 mt-4 d-flex flex-column">
<Row>
<Card className="pt-4 px-4 inner-box-shadow">
<div className="d-flex align-items-start">
Expand All @@ -72,11 +103,19 @@ function Bookkeeper() {
<Card.Body className="px-0 pt-0">
Track inflows and outflow events over time.
<Row className="g-3 flex-wrap">
<Col xs={12} md={12} lg={12} xl={6} className="d-flex flex-column align-items-start">
<SatsFlowInfo label={'Inflow this month'} value={-90000000000} />
<Col
lg={12}
xl={6}
className="d-flex flex-column align-items-start"
>
<SatsFlowInfo label={'Inflow this month'} value={bookkeeperLandingData?.satsFlowSummary.inflows || 0} />
</Col>
<Col xs={12} md={12} lg={12} xl={6} className="d-flex flex-column align-items-start">
<SatsFlowInfo label={'Outflow this month'} value={4000000000} />
<Col
lg={12}
xl={6}
className="d-flex flex-column align-items-start"
>
<SatsFlowInfo label={'Outflow this month'} value={-(bookkeeperLandingData?.satsFlowSummary.outflows || 0)} />
</Col>
</Row>
</Card.Body>
Expand Down Expand Up @@ -108,20 +147,16 @@ function Bookkeeper() {
<Card.Body className="px-0 pt-0">
Track channel routing performance.
<VolumeInfo
bestRoutingChannel={
'41b2aeea3791fcd38d9d1e0183075868808fad06092b3224e91b8b16a4ec3a6b'
}
worstRoutingChannel={
'41b2aeea3791fcd38d9d1e0183075868808fad06092b3224e91b8b16a4ec3a6b'
}
bestRoutingChannel={bookkeeperLandingData?.volumeSummary.mostTrafficChannel}
worstRoutingChannel={bookkeeperLandingData?.volumeSummary.leastTrafficChannel}
/>
</Card.Body>
<Card.Footer className="mt-3 mb-3 d-flex justify-content-end">
<button
tabIndex={1}
type="submit"
className="btn-rounded bg-primary"
onClick={() => navigate('/bookkeeper/satsflow')}
onClick={() => navigate('/bookkeeper/volume')}
>
View More
<ActionSVG className="ms-3" />
Expand All @@ -136,6 +171,6 @@ function Bookkeeper() {
</Container>
</div>
);
}
};

export default Bookkeeper;
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import React from 'react';
import './VolumeInfo.scss';

interface VolumeInfoProps {
bestRoutingChannel: string;
worstRoutingChannel: string;
bestRoutingChannel?: string;
worstRoutingChannel?: string;
}

const VolumeInfo: React.FC<VolumeInfoProps> = ({ bestRoutingChannel, worstRoutingChannel }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as d3 from "d3";
import { format } from 'd3-format';
import { useRef, useEffect } from "react";
import { useRef, useEffect, useMemo } from "react";
import './SatsFlowGraph.scss';
import { SatsFlow, SatsFlowPeriod, TagGroup } from "../../../../types/lightning-satsflow.type";
import { BALANCE_FORMAT } from '../../../../utilities/constants';

function SatsFlowGraph({ satsFlowData, width }: { satsFlowData: SatsFlow, width: number }) {
const d3Container = useRef(null);
const tooltipRef = useRef<HTMLDivElement | null>(null);
const formatSats = useMemo(() => format(BALANCE_FORMAT), []);

useEffect(() => {
if (d3Container.current && satsFlowData.periods.length > 0) {
Expand All @@ -17,7 +19,6 @@ function SatsFlowGraph({ satsFlowData, width }: { satsFlowData: SatsFlow, width:
const margin = { top: 10, right: 30, bottom: 30, left: 100 };
const innerWidth = outerWidth - margin.left - margin.right;
const innerHeight = outerHeight - margin.top - margin.bottom;
const formatSats = format(',.3f');

const { maxInflowSat, maxOutflowSat } = findMaxInflowAndOutflow(satsFlowData);

Expand Down Expand Up @@ -114,7 +115,11 @@ function SatsFlowGraph({ satsFlowData, width }: { satsFlowData: SatsFlow, width:
Net Inflow: ${formatSats(tagGroup.netInflowSat)}
Credits: ${formatSats(tagGroup.creditSat)}
Debits: ${formatSats(tagGroup.debitSat)}
Volume: ${formatSats(tagGroup.volumeSat)}`
Volume: ${formatSats(tagGroup.volumeSat)}
Period Inflow: ${formatSats(period.inflowSat)}
Period Outflow: ${formatSats(period.outflowSat)}
Period Net Inflow: ${formatSats(period.netInflowSat)}
Period Volume: ${formatSats(period.totalVolumeSat)}`
);
})
.on("mousemove", function (event) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,7 @@ const SatsFlowRoot = (props) => {
};

const fetchSatsFlowData = useCallback(async (timeGranularity: TimeGranularity, hideZeroActivityPeriods: boolean, startDate?: Date, endDate?: Date) => {
let startTimestamp = 1;
let endTimestamp = Math.floor(new Date().getTime());

if (startDate != null) {
startTimestamp = Math.floor(startDate.getTime() / 1000);
}
if (endDate != null) {
endTimestamp = Math.floor(endDate.getTime() / 1000);
}

getSatsFlow(timeGranularity, hideZeroActivityPeriods, startTimestamp, endTimestamp)
getSatsFlow(timeGranularity, hideZeroActivityPeriods, startDate, endDate)
.then((response: SatsFlow) => {
setSatsFlowData(response);
})
Expand Down
81 changes: 75 additions & 6 deletions apps/frontend/src/hooks/use-http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { faDollarSign } from '@fortawesome/free-solid-svg-icons';
import { isCompatibleVersion } from '../utilities/data-formatters';
import { BalanceSheetSQL, SatsFlowSQL, VolumeSQL } from '../sql/bookkeeper-sql';
import { transformToBalanceSheet, transformToSatsFlow, transformToVolumeData } from '../sql/bookkeeper-transform';
import { BookkeeperLandingData } from '../types/lightning-bookkeeper-landing.type';

let intervalID;
let localAuthStatus: any = null;
Expand Down Expand Up @@ -163,26 +164,93 @@ const useHttp = () => {
const btcDeposit = () => {
return sendRequest(false, 'post', '/cln/call', { 'method': 'newaddr', 'params': { 'addresstype': 'bech32' } });
};

const getBookkeeperLanding = async (): Promise<BookkeeperLandingData> => {
const balanceSheetData = await getBalanceSheet(TimeGranularity.MONTHLY, true, undefined, new Date());
const satsFlowData = await getSatsFlow(TimeGranularity.MONTHLY, true, undefined, new Date());
const volumeData = await getVolumeData();

const latestBalanceSheetPeriod = balanceSheetData.periods[balanceSheetData.periods.length - 1];
const balanceInWallet = latestBalanceSheetPeriod.accounts.filter(() => "wallet")[0].balance;
const balanceInChannels = latestBalanceSheetPeriod.accounts
.filter(account => account.account !== "wallet")
.reduce((sum, account) => sum + account.balance, 0);
const numberOfChannels = latestBalanceSheetPeriod.accounts
.filter(account => account.account !== "wallet")
.reduce((sum) => sum + 1, 0);

const latestSatsFlowPeriod = satsFlowData.periods[satsFlowData.periods.length - 1];
const inflowsThisMonth = latestSatsFlowPeriod.inflowSat;
const outflowsThisMonth = latestSatsFlowPeriod.outflowSat;


return {
balanceSheetSummary: {
balanceInWallet: balanceInWallet,
balanceInChannels: balanceInChannels,
numberOfChannels: numberOfChannels,
},
satsFlowSummary: {
inflows: inflowsThisMonth,
outflows: outflowsThisMonth,
},
volumeSummary: {
mostTrafficChannel: "best",
leastTrafficChannel: "worst",
}
};
};

/**
* Gets Balance Sheet data.
* @param timeGranularity - Group data by this time granularity.
* @param startTimestamp - If specified, the starting range for the data.
* @param endTimestamp - The ending range for the data.
* @param hideZeroActivityPeriods - Hide bars where balance did not change.
* @param startTimestamp - The starting range for the data if specified, else uses beginning.
* @param endTimestamp - The ending range for the data if specified, else uses now.
* @returns Returns balance data grouped in periods of the specified time granularity.
*/
const getBalanceSheet = (timeGranularity: TimeGranularity, hideZeroActivityPeriods: Boolean, startTimestamp: number, endTimestamp: number) => {
const getBalanceSheet = (timeGranularity: TimeGranularity, hideZeroActivityPeriods: Boolean, startDate?: Date, endDate?: Date) => {
let startTimestamp = 1;
let endTimestamp = Math.floor(new Date().getTime());

if (startDate != null) {
startTimestamp = Math.floor(startDate.getTime() / 1000);
}
if (endDate != null) {
endTimestamp = Math.floor(endDate.getTime() / 1000);
}
return sendRequest(false, 'post', '/cln/call', { 'method': 'sql', 'params': [BalanceSheetSQL] })
.then((response) => transformToBalanceSheet(response.data, timeGranularity, hideZeroActivityPeriods, startTimestamp, endTimestamp));
};

const getSatsFlow = (timeGranularity: TimeGranularity, hideZeroActivityPeriods: boolean, startTimestamp: number, endTimestamp: number) => {
return sendRequest(false, 'post', 'cln/call', { 'method': 'sql', 'params': [SatsFlowSQL(startTimestamp, endTimestamp)] })
/**
* Gets Sats Flow data.
* @param timeGranularity - Group data by this time granularity.
* @param hideZeroActivityPeriods - Hide bars where balance did not change.
* @param startTimestamp - The starting range for the data if specified, else uses beginning.
* @param endTimestamp - The ending range for the data, else uses now.
* @returns Returns sats flow data grouped in periods of the specified time granularity.
*/
const getSatsFlow = (timeGranularity: TimeGranularity, hideZeroActivityPeriods: boolean, startDate?: Date, endDate?: Date) => {
let startTimestamp = 1;
let endTimestamp = Math.floor(new Date().getTime());

if (startDate != null) {
startTimestamp = Math.floor(startDate.getTime() / 1000);
}
if (endDate != null) {
endTimestamp = Math.floor(endDate.getTime() / 1000);
}
return sendRequest(false, 'post', '/cln/call', { 'method': 'sql', 'params': [SatsFlowSQL(startTimestamp, endTimestamp)] })
.then((response) => transformToSatsFlow(response.data, timeGranularity, hideZeroActivityPeriods));
};

/**
* Gets Volume Chart data.
* @returns Returns balance data grouped in periods of the specified time granularity.
*/
const getVolumeData = () => {
return sendRequest(false, 'post', 'cln/call', { 'method': 'sql', 'params': [VolumeSQL] })
return sendRequest(false, 'post', '/cln/call', { 'method': 'sql', 'params': [VolumeSQL] })
.then((response) => transformToVolumeData(response.data));
};

Expand Down Expand Up @@ -330,6 +398,7 @@ const useHttp = () => {
decodeInvoice,
fetchInvoice,
createInvoiceRune,
getBookkeeperLanding,
getBalanceSheet,
getSatsFlow,
getVolumeData,
Expand Down
14 changes: 0 additions & 14 deletions apps/frontend/src/sql/bookkeeper-transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,17 +457,3 @@ function getTag(event: SatsFlowEvent): string {
return event.tag;
}
}

/**
* Compare BalanceSheetRow[] objects.
*
* @param rowA - The first set of rows to compare.
* @param rowB - The second set of rows to compare.
* @returns Returns true if both lists of rows are equal.
*/
function areBalanceSheetRowsEqual(rowsA: BalanceSheetRow[], rowsB: BalanceSheetRow[]): boolean {
if (!rowsA || !rowsB || rowsA.length !== rowsB.length) {
return false;
}
return rowsA.every((row, index) => JSON.stringify(row) === JSON.stringify(rowsB[index]));
}
22 changes: 22 additions & 0 deletions apps/frontend/src/types/lightning-bookkeeper-landing.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export type BookkeeperLandingData = {
balanceSheetSummary: BalanceSheetSummary;
satsFlowSummary: SatsFlowSummary;
volumeSummary: VolumeSummary;
};

export type BalanceSheetSummary = {
balanceInWallet: number;
balanceInChannels: number;
numberOfChannels: number;
};

export type SatsFlowSummary = {
inflows: number;
outflows: number;
};

export type VolumeSummary = {
mostTrafficChannel: string;
leastTrafficChannel: string;
}

Empty file modified entrypoint.sh
100644 → 100755
Empty file.

0 comments on commit 4d2b599

Please sign in to comment.