Skip to content

Commit ac29f38

Browse files
committed
Show sum of channel balances in token view
1 parent 2af6398 commit ac29f38

11 files changed

Lines changed: 168 additions & 74 deletions

src/app/components/channel-list/channel-list.component.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,7 @@ import {
1818
} from '../open-dialog/open-dialog.component';
1919
import { RaidenConfig } from '../../services/raiden.config';
2020
import { RaidenService } from '../../services/raiden.service';
21-
import {
22-
flatMap,
23-
map,
24-
takeUntil,
25-
shareReplay,
26-
debounceTime,
27-
} from 'rxjs/operators';
21+
import { flatMap, map, takeUntil, debounceTime } from 'rxjs/operators';
2822
import { MatDialog } from '@angular/material/dialog';
2923
import { Animations } from '../../animations/animations';
3024
import { TokenPollingService } from '../../services/token-polling.service';
@@ -33,7 +27,6 @@ import { SharedService } from '../../services/shared.service';
3327
import { matchesChannel, matchesContact } from '../../shared/keyword-matcher';
3428
import { AddressBookService } from '../../services/address-book.service';
3529
import { Contact } from '../../models/contact';
36-
import { TokenUtils } from '../../utils/token.utils';
3730

3831
@Component({
3932
selector: 'app-channel-list',
@@ -67,12 +60,7 @@ export class ChannelListComponent implements OnInit, OnDestroy, AfterViewInit {
6760
private sharedService: SharedService,
6861
private addressBookService: AddressBookService
6962
) {
70-
this.tokens$ = this.tokenPollingService.tokens$.pipe(
71-
map((tokens) =>
72-
tokens.sort((a, b) => TokenUtils.compareTokens(a, b))
73-
),
74-
shareReplay({ refCount: true, bufferSize: 1 })
75-
);
63+
this.tokens$ = this.tokenPollingService.tokens$;
7664
}
7765

7866
ngOnInit() {

src/app/components/token-network-selector/token-network-selector.component.spec.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ describe('TokenNetworkSelectorComponent', () => {
3636
name: 'Another Test Token2',
3737
balance: new BigNumber(0),
3838
});
39-
const tokens = [notOwnedToken, connectedToken, ownedToken];
39+
const tokens = [connectedToken, ownedToken, notOwnedToken];
4040

4141
beforeEach(async(() => {
4242
const tokenPollingMock = stub<TokenPollingService>();
@@ -70,24 +70,6 @@ describe('TokenNetworkSelectorComponent', () => {
7070
expect(component).toBeTruthy();
7171
});
7272

73-
it('should sort tokens first by connection, then owned and last not owned', (done) => {
74-
component.tokens$.subscribe((value) => {
75-
expect(value[0].address).toBe(
76-
connectedToken.address,
77-
'connection token should go first'
78-
);
79-
expect(value[1].address).toBe(
80-
ownedToken.address,
81-
'owned token should go second'
82-
);
83-
expect(value[2].address).toBe(
84-
notOwnedToken.address,
85-
'not owned token should go third'
86-
);
87-
done();
88-
});
89-
});
90-
9173
it('should be able to only show connected tokens', (done) => {
9274
component.onlyConnectedTokens = true;
9375
fixture.detectChanges();

src/app/components/token-network-selector/token-network-selector.component.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export class TokenNetworkSelectorComponent implements ControlValueAccessor {
4545
? value.filter((token) => !!token.connected)
4646
: value
4747
),
48-
map((value) => value.sort(TokenUtils.compareTokens)),
4948
share()
5049
);
5150
}

src/app/components/token/token.component.html

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,14 +67,16 @@
6767
fxFlex="7 0 0"
6868
class="overview__item"
6969
>
70-
<ng-container *ngIf="token.connected; else no_deposits">
70+
<ng-container
71+
*ngIf="token.sumChannelBalances; else no_deposits"
72+
>
7173
<span
7274
*ngIf="
73-
token.connected?.sum_deposits
74-
| decimal: token.decimals as sumDeposits
75+
token.sumChannelBalances
76+
| decimal: token.decimals as sumChannelBalances
7577
"
76-
[matTooltip]="sumDeposits"
77-
>{{ sumDeposits | displayDecimals: 3 }}</span
78+
[matTooltip]="sumChannelBalances"
79+
>{{ sumChannelBalances | displayDecimals: 3 }}</span
7880
>
7981
</ng-container>
8082
<ng-template #no_deposits>
@@ -188,14 +190,17 @@
188190

189191
<div class="card__network" fxLayout="row">
190192
<div *ngIf="!isAllNetworksView; else all_networks">
191-
<ng-container *ngIf="selectedToken.connected; else no_deposits">
193+
<ng-container
194+
*ngIf="selectedToken.sumChannelBalances; else no_deposits"
195+
>
192196
<span
193197
*ngIf="
194-
selectedToken.connected?.sum_deposits
195-
| decimal: selectedToken.decimals as sumDeposits
198+
selectedToken.sumChannelBalances
199+
| decimal
200+
: selectedToken.decimals as sumChannelBalances
196201
"
197-
[matTooltip]="sumDeposits"
198-
>{{ sumDeposits | displayDecimals: 3 }}</span
202+
[matTooltip]="sumChannelBalances"
203+
>{{ sumChannelBalances | displayDecimals: 3 }}</span
199204
>
200205
</ng-container>
201206
<ng-template #no_deposits>

src/app/components/token/token.component.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
ConfirmationDialogComponent,
1010
} from '../confirmation-dialog/confirmation-dialog.component';
1111
import { MatDialog } from '@angular/material/dialog';
12-
import { flatMap, takeUntil, map, shareReplay } from 'rxjs/operators';
12+
import { flatMap, takeUntil, map } from 'rxjs/operators';
1313
import { EMPTY, Subject, Observable } from 'rxjs';
1414
import {
1515
PaymentDialogPayload,
@@ -22,7 +22,6 @@ import {
2222
import { PendingTransferPollingService } from '../../services/pending-transfer-polling.service';
2323
import { ChannelPollingService } from '../../services/channel-polling.service';
2424
import { SelectedTokenService } from '../../services/selected-token.service';
25-
import { TokenUtils } from '../../utils/token.utils';
2625
import { RegisterDialogComponent } from '../register-dialog/register-dialog.component';
2726
import { PaymentHistoryPollingService } from '../../services/payment-history-polling.service';
2827

@@ -48,12 +47,7 @@ export class TokenComponent implements OnInit, OnDestroy {
4847
private selectedTokenService: SelectedTokenService,
4948
private paymentHistoryPollingService: PaymentHistoryPollingService
5049
) {
51-
this.tokens$ = this.tokenPollingService.tokens$.pipe(
52-
map((tokens) =>
53-
tokens.sort((a, b) => TokenUtils.compareTokens(a, b))
54-
),
55-
shareReplay({ refCount: true, bufferSize: 1 })
56-
);
50+
this.tokens$ = this.tokenPollingService.tokens$;
5751
}
5852

5953
get production(): boolean {

src/app/models/usertoken.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ export interface UserToken {
88
decimals: number;
99
balance: BigNumber;
1010
connected?: Connection;
11+
sumChannelBalances?: BigNumber;
1112
}

src/app/services/channel-polling.service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { Injectable } from '@angular/core';
22
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
3-
import { scan, switchMap, tap, map, shareReplay, startWith } from 'rxjs/operators';
3+
import {
4+
scan,
5+
switchMap,
6+
tap,
7+
map,
8+
shareReplay,
9+
startWith,
10+
} from 'rxjs/operators';
411
import { Channel } from '../models/channel';
512
import { RaidenConfig } from './raiden.config';
613
import { RaidenService } from './raiden.service';

src/app/services/token-polling.service.spec.ts

Lines changed: 87 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,79 @@ import { HttpClientTestingModule } from '@angular/common/http/testing';
33
import { TokenPollingService } from './token-polling.service';
44
import { RaidenService } from './raiden.service';
55
import { TestProviders } from '../../testing/test-providers';
6-
import Spy = jasmine.Spy;
76
import { UserToken } from '../models/usertoken';
8-
import { of } from 'rxjs';
9-
import { createToken } from '../../testing/test-data';
7+
import { of, BehaviorSubject } from 'rxjs';
8+
import { createToken, createChannel } from '../../testing/test-data';
109
import BigNumber from 'bignumber.js';
10+
import { ChannelPollingService } from './channel-polling.service';
11+
import { stub } from '../../testing/stub';
12+
import { Channel } from '../models/channel';
1113

1214
describe('TokenPollingService', () => {
1315
let raidenService: RaidenService;
14-
let tokenSpy: Spy;
16+
let channelsSubject: BehaviorSubject<Channel[]>;
1517

1618
const token = createToken();
1719
const updatedToken = Object.assign({}, token, {
1820
balance: new BigNumber(50),
1921
});
22+
const notOwnedToken = createToken({ balanc: new BigNumber(0) });
23+
24+
const connectedTokens = [
25+
createToken({
26+
connected: {
27+
channels: 1,
28+
funds: new BigNumber(0),
29+
sum_deposits: new BigNumber(0),
30+
},
31+
}),
32+
createToken({
33+
connected: {
34+
channels: 2,
35+
funds: new BigNumber(0),
36+
sum_deposits: new BigNumber(0),
37+
},
38+
}),
39+
];
40+
const channels = [
41+
createChannel({
42+
userToken: connectedTokens[0],
43+
balance: new BigNumber(50),
44+
}),
45+
createChannel({
46+
userToken: connectedTokens[1],
47+
balance: new BigNumber(15),
48+
}),
49+
createChannel({
50+
userToken: connectedTokens[1],
51+
balance: new BigNumber(5),
52+
}),
53+
];
54+
55+
beforeEach(() => {
56+
const channelPollingMock = stub<ChannelPollingService>();
57+
channelsSubject = new BehaviorSubject<Channel[]>(channels);
58+
// @ts-ignore
59+
channelPollingMock.channels$ = channelsSubject.asObservable();
2060

21-
beforeEach(() =>
2261
TestBed.configureTestingModule({
2362
imports: [HttpClientTestingModule],
2463
providers: [
2564
TokenPollingService,
65+
ChannelPollingService,
2666
RaidenService,
2767
TestProviders.MockRaidenConfigProvider(),
2868
TestProviders.AddressBookStubProvider(),
69+
{
70+
provide: ChannelPollingService,
71+
useValue: channelPollingMock,
72+
},
2973
],
30-
})
31-
);
74+
});
75+
});
3276

3377
beforeEach(() => {
3478
raidenService = TestBed.inject(RaidenService);
35-
tokenSpy = spyOn(raidenService, 'getTokens').and.returnValues(
36-
of([token]),
37-
of([token]),
38-
of([updatedToken])
39-
);
4079
});
4180

4281
it('should be created', inject(
@@ -49,6 +88,9 @@ describe('TokenPollingService', () => {
4988
it('should refresh the tokens every polling interval', inject(
5089
[TokenPollingService],
5190
fakeAsync((service: TokenPollingService) => {
91+
const tokenSpy = spyOn(raidenService, 'getTokens').and.returnValue(
92+
of([token])
93+
);
5294
const subscription = service.tokens$.subscribe(
5395
(tokens: UserToken[]) => {
5496
expect(tokens).toEqual([token]);
@@ -70,6 +112,11 @@ describe('TokenPollingService', () => {
70112
it('should get updates for a token', inject(
71113
[TokenPollingService],
72114
fakeAsync((service: TokenPollingService) => {
115+
spyOn(raidenService, 'getTokens').and.returnValues(
116+
of([token]),
117+
of([token]),
118+
of([updatedToken])
119+
);
73120
let emittedTimes = 0;
74121
const subscription = service
75122
.getTokenUpdates(token.address)
@@ -87,4 +134,32 @@ describe('TokenPollingService', () => {
87134
flush();
88135
})
89136
));
137+
138+
it('should calculate the sum of channel balances for each token', inject(
139+
[TokenPollingService],
140+
(service: TokenPollingService) => {
141+
spyOn(raidenService, 'getTokens').and.returnValue(
142+
of(connectedTokens)
143+
);
144+
service.tokens$.subscribe((tokens: UserToken[]) => {
145+
expect(tokens[0].sumChannelBalances).toEqual(new BigNumber(50));
146+
expect(tokens[1].sumChannelBalances).toEqual(new BigNumber(20));
147+
});
148+
}
149+
));
150+
151+
it('should sort tokens first by sum of channel balances, then owned and last not owned', inject(
152+
[TokenPollingService],
153+
(service: TokenPollingService) => {
154+
spyOn(raidenService, 'getTokens').and.returnValue(
155+
of(connectedTokens.concat([token, notOwnedToken]))
156+
);
157+
service.tokens$.subscribe((tokens: UserToken[]) => {
158+
expect(tokens[0].address).toBe(connectedTokens[0].address);
159+
expect(tokens[1].address).toBe(connectedTokens[1].address);
160+
expect(tokens[2].address).toBe(token.address);
161+
expect(tokens[3].address).toBe(notOwnedToken.address);
162+
});
163+
}
164+
));
90165
});

src/app/services/token-polling.service.ts

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { Injectable } from '@angular/core';
22
import { RaidenService } from './raiden.service';
3-
import { BehaviorSubject, Observable } from 'rxjs';
3+
import { BehaviorSubject, Observable, combineLatest } from 'rxjs';
44
import { UserToken } from '../models/usertoken';
55
import { tap, switchMap, shareReplay, startWith, map } from 'rxjs/operators';
66
import { backoff } from '../shared/backoff.operator';
77
import { RaidenConfig } from './raiden.config';
8+
import { ChannelPollingService } from './channel-polling.service';
9+
import { Channel } from '../models/channel';
10+
import BigNumber from 'bignumber.js';
11+
import { TokenUtils } from '../utils/token.utils';
812

913
@Injectable({
1014
providedIn: 'root',
@@ -19,15 +23,26 @@ export class TokenPollingService {
1923

2024
constructor(
2125
private raidenService: RaidenService,
22-
private raidenConfig: RaidenConfig
26+
private raidenConfig: RaidenConfig,
27+
private channelPollingService: ChannelPollingService
2328
) {
2429
let timeout;
2530
this.tokens$ = this.tokensSubject.pipe(
2631
tap(() => {
2732
clearTimeout(timeout);
2833
this.refreshingSubject.next(true);
2934
}),
30-
switchMap(() => this.raidenService.getTokens(true)),
35+
switchMap(() =>
36+
combineLatest([
37+
this.raidenService.getTokens(true),
38+
this.channelPollingService.channels$,
39+
])
40+
),
41+
map(([tokens, channels]) =>
42+
this.calculateSumOfBalances(tokens, channels).sort((a, b) =>
43+
TokenUtils.compareTokens(a, b)
44+
)
45+
),
3146
tap(() => {
3247
timeout = setTimeout(
3348
() => this.refresh(),
@@ -62,4 +77,30 @@ export class TokenPollingService {
6277
})
6378
);
6479
}
80+
81+
private calculateSumOfBalances(
82+
tokens: UserToken[],
83+
channels: Channel[]
84+
): UserToken[] {
85+
const summedBalances: { [tokenAddress: string]: BigNumber } = {};
86+
87+
channels.forEach((channel) => {
88+
const tokenAddress = channel.token_address;
89+
if (!summedBalances[tokenAddress]) {
90+
summedBalances[tokenAddress] = channel.balance;
91+
return;
92+
}
93+
summedBalances[tokenAddress] = summedBalances[tokenAddress].plus(
94+
channel.balance
95+
);
96+
});
97+
98+
Object.entries(summedBalances).forEach(([tokenAddress, balance]) => {
99+
const token = tokens.find((item) => tokenAddress === item.address);
100+
if (token) {
101+
token.sumChannelBalances = balance;
102+
}
103+
});
104+
return tokens;
105+
}
65106
}

0 commit comments

Comments
 (0)