Skip to content

Commit aa4d6a6

Browse files
evansmjShahanaFarooqui
authored andcommitted
Restore Minutely Time Granularity
Adds a Minutely time granularity option so that users can view minute by minute events. Added AccountEventsTable tests. Fixed typos and updated component data-testid's. Uncheck and Disable `Show Zero Activity` on Hourly and Minutely granularity due to unneccessarily large break points.
1 parent 910ed4f commit aa4d6a6

File tree

10 files changed

+143
-19
lines changed

10 files changed

+143
-19
lines changed

apps/frontend/src/components/bookkeeper/AccountEvents/AccountEventsTable/AccountEventsTable.scss

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@import '../../../../styles/constants.scss';
22

3-
.table-container {
3+
.account-events-table-container {
44
padding: 0 !important;
55
border-radius: 1rem;
66
overflow: hidden;
@@ -94,7 +94,7 @@
9494
}
9595

9696
@include color-mode(dark) {
97-
.table-container {
97+
.account-events-table-container {
9898
border: 1px solid $border-color-dark;
9999
& .expandable-table {
100100
& tr.expandable-header-row {
Lines changed: 95 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,101 @@
1-
import { screen } from '@testing-library/react';
1+
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
22
import { renderWithProviders } from '../../../../utilities/test-utilities/mockStore';
33
import { mockBKPRAccountEvents, mockAppStore } from '../../../../utilities/test-utilities/mockData';
44
import AccountEventsTable from './AccountEventsTable';
55

6-
describe.skip('Account Events Table component ', () => {
7-
it('should be in the document', async () => {
8-
await renderWithProviders(<AccountEventsTable periods={mockBKPRAccountEvents.periods} />, { preloadedState: mockAppStore, initialRoute: ['/bookkeeper/accountevents'] });
9-
expect(screen.getByTestId('account-events-table-container')).not.toBeEmptyDOMElement();
6+
jest.mock('react-perfect-scrollbar', () => ({ children }) => <div>{children}</div>);
7+
8+
describe('Account Events Table component ', () => {
9+
beforeEach(() => {
10+
global.ResizeObserver = jest.fn().mockImplementation(() => ({
11+
observe: jest.fn(),
12+
unobserve: jest.fn(),
13+
disconnect: jest.fn(),
14+
}));
15+
});
16+
17+
it('should render the dashboard container', async () => {
18+
await renderWithProviders(<AccountEventsTable periods={mockBKPRAccountEvents.periods} />, {
19+
preloadedState: mockAppStore,
20+
initialRoute: ['/bookkeeper/accountevents'],
21+
});
22+
expect(await screen.findByTestId('account-events-table-container')).toBeInTheDocument();
23+
});
24+
25+
it('renders AccountEventsTable with correct data', async () => {
26+
await renderWithProviders(<AccountEventsTable periods={mockBKPRAccountEvents.periods} />, {
27+
preloadedState: mockAppStore,
28+
initialRoute: ['/bookkeeper/accountevents'],
29+
useRouter: false,
30+
});
31+
32+
expect(screen.getByText('2025-04-20')).toBeInTheDocument();
33+
expect(screen.getAllByText('wallet')).toHaveLength(2); //short_channel_id and account
34+
expect(screen.getByText('n/a')).toBeInTheDocument();
35+
});
36+
37+
it('should render all periods as expanded by default', async () => {
38+
await renderWithProviders(<AccountEventsTable periods={mockBKPRAccountEvents.periods} />, {
39+
preloadedState: mockAppStore,
40+
initialRoute: ['/bookkeeper/accountevents'],
41+
useRouter: false,
42+
});
43+
44+
mockBKPRAccountEvents.periods.forEach(period => {
45+
expect(screen.getByText(period.period_key)).toBeInTheDocument();
46+
period.accounts.forEach(account => {
47+
expect(screen.getByText("Short Channel ID")).toBeInTheDocument();
48+
expect(screen.getByText(account.remote_alias)).toBeInTheDocument();
49+
});
50+
});
51+
});
52+
53+
it('should collapse and expand a row on toggle click', async () => {
54+
await renderWithProviders(<AccountEventsTable periods={mockBKPRAccountEvents.periods} />, {
55+
preloadedState: mockAppStore,
56+
initialRoute: ['/bookkeeper/accountevents'],
57+
useRouter: false,
58+
});
59+
60+
const periodKey = mockBKPRAccountEvents.periods[0].period_key;
61+
62+
const periodRow = screen.getByText(periodKey).closest('tr');
63+
expect(periodRow).not.toBeNull();
64+
const toggleButton = within(periodRow!).getByRole('button');
65+
66+
//check that the row is expanded
67+
await waitFor(() => {
68+
expect(screen.getByText('Short Channel ID')).toBeVisible();
69+
});
70+
71+
//collapse
72+
fireEvent.click(toggleButton);
73+
74+
await waitFor(() => {
75+
const content = screen.queryByText('Short Channel ID');
76+
expect(content).not.toBeVisible();
77+
});
78+
79+
//expand
80+
fireEvent.click(toggleButton);
81+
82+
await waitFor(() => {
83+
expect(screen.getByText('Short Channel ID')).toBeVisible();
84+
});
85+
});
86+
87+
it('should correctly render balance percentage values', async () => {
88+
await renderWithProviders(<AccountEventsTable periods={mockBKPRAccountEvents.periods} />, {
89+
preloadedState: mockAppStore,
90+
initialRoute: ['/bookkeeper/accountevents'],
91+
useRouter: false,
92+
});
93+
94+
const { balance_msat } = mockBKPRAccountEvents.periods[0].accounts[0];
95+
const { total_balance_across_accounts } = mockBKPRAccountEvents.periods[0];
96+
const expectedPercentage =
97+
((balance_msat / total_balance_across_accounts) * 100).toFixed(2) + '%';
98+
99+
expect(screen.getByText(expectedPercentage)).toBeInTheDocument();
10100
});
11101
});

apps/frontend/src/components/bookkeeper/AccountEvents/AccountEventsTable/AccountEventsTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function AccountEventsTable({periods}: {periods: AccountEventsPeriod[]}) {
2828
};
2929

3030
return (
31-
<div className='table-container'>
31+
<div className='account-events-table-container' data-testid="account-events-table-container">
3232
<PerfectScrollbar>
3333
<Table className='expandable-table'>
3434
<thead className='expandable-head'>

apps/frontend/src/components/bookkeeper/BkprHome/BkprHome.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,16 @@ describe('Bookkeeper Component', () => {
3030
preloadedState: mockAppStore,
3131
});
3232
expect(screen.getByTestId('account-event-info-container')).toBeInTheDocument();
33-
expect(screen.getByTestId('satsflow-volume-container')).toBeInTheDocument();
33+
expect(screen.getByTestId('satsflow-info-container')).toBeInTheDocument();
34+
expect(screen.getByTestId('volume-info-container')).toBeInTheDocument();
3435
});
3536

3637
it('should not render overview/cards if path is not /bookkeeper', async () => {
3738
setMockedLocation({ pathname: '/bookkeeper/satsflow' });
3839
await renderWithProviders(<Bookkeeper />, { preloadedState: mockAppStore, initialRoute: ['/bookkeeper/satsflow'] });
3940
expect(screen.queryByTestId('account-event-info-container')).not.toBeInTheDocument();
40-
expect(screen.queryByTestId('satsflow-volume-container')).not.toBeInTheDocument();
41+
expect(screen.queryByTestId('satsflow-info-container')).not.toBeInTheDocument();
42+
expect(screen.queryByTestId('volume-info-container')).not.toBeInTheDocument();
4143
});
4244

4345
it('should display error message if nodeInfo has error', async () => {

apps/frontend/src/components/bookkeeper/BkprHome/BkprHome.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ const Bookkeeper = () => {
3939
</Col>
4040
</Row>
4141
<Row className='px-3'>
42-
<Col xs={12} lg={6} className='cards-container' data-testid='account-event-info-container'>
42+
<Col xs={12} lg={6} className='cards-container'>
4343
<AccountEventsInfo />
4444
</Col>
45-
<Col xs={12} lg={6} className='cards-container d-flex flex-column justify-content-between' data-testid='satsflow-volume-container'>
45+
<Col xs={12} lg={6} className='cards-container d-flex flex-column justify-content-between'>
4646
<SatsFlowInfo />
4747
<VolumeInfo />
4848
</Col>

apps/frontend/src/components/bookkeeper/Volume/VolumeRoot.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { mockAppStore } from '../../../utilities/test-utilities/mockData';
33
import { renderWithProviders } from '../../../utilities/test-utilities/mockStore';
44
import VolumeRoot from './VolumeRoot';
55

6-
describe('Sats Flow component ', () => {
6+
describe('Volume component ', () => {
77
beforeEach(() => {
88
global.ResizeObserver = jest.fn().mockImplementation(() => ({
99
observe: jest.fn(),

apps/frontend/src/components/shared/DataFilterOptions/DataFilterOptions.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ const DataFilterOptions = (props: {filter: string, onShowZeroActivityChange: (sh
5656
const onTimeGranularityChanged = (eventKey: string | null) => {
5757
if (eventKey !== null) {
5858
setTimeGranularity(eventKey as TimeGranularity);
59+
if (eventKey === TimeGranularity.MINUTELY || eventKey === TimeGranularity.HOURLY) {
60+
setShowZeroActivityPeriods(false);
61+
props.onShowZeroActivityChange(false);
62+
}
5963
}
6064
};
6165

@@ -116,6 +120,7 @@ const DataFilterOptions = (props: {filter: string, onShowZeroActivityChange: (sh
116120
tabIndex={4}
117121
onChange={showZeroActivityPeriodsChangeHandler}
118122
checked={showZeroActivityPeriods}
123+
disabled={timeGranularity === TimeGranularity.MINUTELY || timeGranularity === TimeGranularity.HOURLY}
119124
inline
120125
className="mt-2 fs-base fw-light"
121126
label="Show Zero Activity"

apps/frontend/src/store/bkprSelectors.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,16 @@ export const selectAccountEventPeriods = createSelector(
4444
(events) => events.periods || []
4545
);
4646

47+
export const selectAccountEventsLoading = createSelector(
48+
selectAccountEvents,
49+
(accountEvents) => accountEvents.isLoading
50+
);
51+
52+
export const selectAccountEventsError = createSelector(
53+
selectAccountEvents,
54+
(accountEvents) => accountEvents.error
55+
)
56+
4757
export const selectSatsFlow = createSelector(
4858
selectBKPRState,
4959
(bkpr) => bkpr.satsFlow

apps/frontend/src/utilities/constants.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export enum PaymentType {
135135
}
136136

137137
export enum TimeGranularity {
138+
MINUTELY = 'Minutely',
138139
HOURLY = 'Hourly',
139140
DAILY = 'Daily',
140141
WEEKLY = 'Weekly',
@@ -147,6 +148,7 @@ const SECONDS_IN_DAY = 86400;
147148

148149
// Pre-calculated durations in seconds
149150
const GRANULARITY_DURATIONS = {
151+
[TimeGranularity.MINUTELY]: 60,
150152
[TimeGranularity.HOURLY]: 3600,
151153
[TimeGranularity.DAILY]: SECONDS_IN_DAY,
152154
[TimeGranularity.WEEKLY]: SECONDS_IN_DAY * 7,
@@ -170,6 +172,8 @@ export function getPeriodKey(timeGranularity: TimeGranularity, timestamp: number
170172
const date = moment.unix(timestamp);
171173

172174
switch (timeGranularity) {
175+
case TimeGranularity.MINUTELY:
176+
return date.format('YYYY-MM-DD HH:mm')
173177
case TimeGranularity.HOURLY:
174178
return date.format('YYYY-MM-DD HH:00');
175179
case TimeGranularity.DAILY:
@@ -190,6 +194,10 @@ export function getTimestampFromPeriodKey(timeGranularity: TimeGranularity, peri
190194
let startOfUnit: moment.unitOfTime.StartOf;
191195

192196
switch (timeGranularity) {
197+
case TimeGranularity.MINUTELY:
198+
format = 'YYYY-MM-DD HH:mm'
199+
startOfUnit = 'minute';
200+
break;
193201
case TimeGranularity.HOURLY:
194202
format = 'YYYY-MM-DD HH';
195203
startOfUnit = 'hour';
@@ -239,6 +247,7 @@ export const getTimestampWithGranularity = (
239247

240248
export function getFormatForGranularity(granularity: TimeGranularity): string {
241249
switch (granularity) {
250+
case TimeGranularity.MINUTELY: return 'YYYY-MM-DD HH:mm'
242251
case TimeGranularity.HOURLY: return 'YYYY-MM-DD HH';
243252
case TimeGranularity.DAILY: return 'YYYY-MM-DD';
244253
case TimeGranularity.WEEKLY: return 'YYYY-MM-DD';
@@ -250,6 +259,7 @@ export function getFormatForGranularity(granularity: TimeGranularity): string {
250259

251260
export function getMomentUnit(granularity: TimeGranularity): moment.unitOfTime.DurationConstructor {
252261
switch (granularity) {
262+
case TimeGranularity.MINUTELY: return 'minute'
253263
case TimeGranularity.HOURLY: return 'hour';
254264
case TimeGranularity.DAILY: return 'day';
255265
case TimeGranularity.WEEKLY: return 'week';

apps/frontend/src/utilities/test-utilities/mockStore.tsx

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { mockAppStore } from './mockData';
1313
interface ExtendedRenderOptions extends Omit<RenderOptions, 'queries'> {
1414
preloadedState?: AppState;
1515
initialRoute?: string[];
16+
useRouter?: Boolean
1617
}
1718

1819
export function createMockStore(route: string, preloadedState?: AppState) {
@@ -68,6 +69,7 @@ export async function renderWithProviders(
6869
{
6970
preloadedState,
7071
initialRoute = ['/'],
72+
useRouter = true,
7173
...renderOptions
7274
}: ExtendedRenderOptions = {}
7375
) {
@@ -79,14 +81,19 @@ export async function renderWithProviders(
7981
v7_relativeSplatPath: true,
8082
v7_normalizeFormMethod: true,
8183
v7_startTransition: true,
82-
} as any
84+
} as any,
8385
});
86+
8487
const Wrapper = ({ children }: PropsWithChildren<{}>) => {
85-
return (
86-
<Provider store={store}>
87-
<RouterProvider router={router} />
88-
</Provider>
89-
);
88+
if (useRouter) {
89+
return (
90+
<Provider store={store}>
91+
<RouterProvider router={router} />
92+
</Provider>
93+
);
94+
}
95+
96+
return <Provider store={store}>{children}</Provider>;
9097
};
9198

9299
let result;

0 commit comments

Comments
 (0)