fix: empty incoming transactions chart

This commit is contained in:
rodribp 2026-07-16 01:37:49 -06:00 committed by Rodrigo Bonilla
parent 29afce710d
commit 581eeaca19
12 changed files with 182 additions and 66 deletions

View file

@ -295,7 +295,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list2H() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -306,7 +306,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list24h() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -317,7 +317,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list1W() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -328,7 +328,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list1M() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -339,7 +339,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list3M() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -350,7 +350,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list6M() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -361,7 +361,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list1Y() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -372,7 +372,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list2Y() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -383,7 +383,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list3Y() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -394,7 +394,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$list4Y() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}
@ -405,7 +405,7 @@ class StatisticsApi {
return this.mapStatisticToOptimizedStatistic(rows as Statistic[]);
} catch (e) {
logger.err('$listAll() error' + (e instanceof Error ? e.message : e));
return [];
throw e;
}
}

View file

@ -22,7 +22,6 @@ class StatisticsRoutes {
private async $getStatisticsByTime(time: '2h' | '24h' | '1w' | '1m' | '3m' | '6m' | '1y' | '2y' | '3y' | '4y' | 'all', req: Request, res: Response) {
res.header('Pragma', 'public');
res.header('Cache-control', 'public');
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
try {
let result;
@ -63,6 +62,11 @@ class StatisticsRoutes {
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
break;
}
if (!res.hasHeader('Expires')) {
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
}
res.json(result);
} catch (e) {
handleError(req, res, 500, 'Failed to get statistics');

View file

@ -55,13 +55,15 @@
<div class="card-body">
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
<h5 class="card-title mt-3" i18n="dashboard.incoming-transactions">Incoming Transactions</h5>
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats">
<div class="mempool-graph">
<app-incoming-transactions-graph
[height]="incomingGraphHeight"
[left]="50"
[right]="20"
[data]="mempoolStats?.weightPerSecond"
[isLoading]="isMempoolStatsLoading"
[windowPreferenceOverride]="'2h'"
[error]="mempoolStatsError"
></app-incoming-transactions-graph>
</div>
</div>

View file

@ -1,6 +1,6 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { combineLatest, merge, Observable, of, Subject, Subscription } from 'rxjs';
import { catchError, filter, map, scan, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
import { catchError, filter, map, retry, scan, shareReplay, startWith, switchMap, tap } from 'rxjs/operators';
import { BlockExtended, OptimizedMempoolStats, TransactionStripped } from '@interfaces/node-api.interface';
import { MempoolInfo, ReplacementInfo } from '@interfaces/websocket.interface';
import { ApiService } from '@app/services/api.service';
@ -47,10 +47,13 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
replacements$: Observable<ReplacementInfo[]>;
latestBlockHeight: number;
mempoolTransactionsWeightPerSecondData: any;
mempoolStats$: Observable<MempoolStatsData>;
mempoolStats: MempoolStatsData;
mempoolStatsError: any;
isMempoolStatsLoading: boolean = true;
transactionsWeightPerSecondOptions: any;
isLoadingWebSocket$: Observable<boolean>;
isLoad: boolean = true;
mempoolStatsSubscription: Subscription;
filterSubscription: Subscription;
mempoolInfoSubscription: Subscription;
currencySubscription: Subscription;
@ -100,6 +103,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
}
ngOnDestroy(): void {
this.mempoolStatsSubscription.unsubscribe();
this.filterSubscription.unsubscribe();
this.mempoolInfoSubscription.unsubscribe();
this.currencySubscription.unsubscribe();
@ -225,13 +229,19 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
this.replacements$ = this.stateService.rbfLatestSummary$;
this.mempoolStats$ = this.stateService.connectionState$
this.mempoolStatsSubscription = this.stateService.connectionState$
.pipe(
filter((state) => state === 2),
tap(() => {
this.isMempoolStatsLoading = true;
this.mempoolStatsError = null;
}),
switchMap(() => this.apiService.list2HStatistics$().pipe(
catchError((e) => {
retry({ count: 3, delay: 1000 }),
catchError((err) => {
this.mempoolStatsError = err;
return of(null);
})
}),
)),
switchMap((mempoolStats) => {
return merge(
@ -240,6 +250,7 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
scan((acc, stats) => {
const now = Date.now() / 1000;
const start = now - (2 * 60 * 60);
this.mempoolStatsError = null;
acc.unshift(stats);
acc = acc.filter(p => p.added >= start);
return acc;
@ -258,8 +269,11 @@ export class CustomDashboardComponent implements OnInit, OnDestroy, AfterViewIni
return null;
}
}),
shareReplay(1),
);
).subscribe((mempoolStats) => {
this.isMempoolStatsLoading = false;
this.mempoolStats = mempoolStats;
this.cd.markForCheck();
});
this.currencySubscription = this.stateService.fiatCurrency$.subscribe((fiat) => {
this.currency = fiat;

View file

@ -1,6 +1,6 @@
<div class="echarts" *browserOnly echarts [initOpts]="mempoolStatsChartInitOption" [options]="mempoolStatsChartOption" (chartRendered)="rendered()"
(chartInit)="onChartInit($event)" [style]="{opacity: isLoading ? 0.5 : 1}">
</div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || (isLoading && !error)">
<div class="spinner-border text-light"></div>
</div>

View file

@ -33,7 +33,8 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
@Input() template: ('widget' | 'advanced') = 'widget';
@Input() windowPreferenceOverride: string;
@Input() outlierCappingEnabled: boolean = false;
@Input() isLoading: boolean;
@Input() isLoading: boolean = true;
@Input() error: any;
mempoolStatsChartOption: EChartsOption = {};
mempoolStatsChartInitOption = {
@ -55,14 +56,18 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
ngOnInit() {
this.rateUnitSub = this.stateService.rateUnits$.subscribe(rateUnits => {
this.weightMode = rateUnits === 'wu';
if (this.data) {
if (this.data && !this.error) {
this.mountChart();
}
});
}
ngOnChanges(): void {
if (!this.data) {
if (this.isLoading) {
return;
}
if (!this.data || (this.data && this.data.series?.[0]?.length === 0) || this.error) {
this.mountMessageOnChart();
return;
}
this.windowPreference = (this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference')) || '2h';
@ -259,8 +264,8 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
],
yAxis: {
max: (value): number => {
let cappedMax = value.max;
if (this.outlierCappingEnabled && value.max >= (this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER)) {
let cappedMax = Number.isFinite(value.max) ? value.max : 0;
if (this.outlierCappingEnabled && cappedMax >= (this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER)) {
cappedMax = Math.round(this.medianVbytesPerSecond * OUTLIERS_MEDIAN_MULTIPLIER);
}
// always show the clearing rate line, plus a small margin
@ -322,6 +327,28 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On
};
}
mountMessageOnChart(): void {
let errorMessage = $localize`:error.general-loading-data:Error loading data.`;
if (!this.error && this.data && this.data.series[0]?.length === 0) { // Empty array
errorMessage = $localize`No data to display yet. Try again later.`;
}
if (this.template === 'widget') {
errorMessage = $localize`Awaiting live update...`;
}
this.mempoolStatsChartOption = {
title: {
textStyle: {
color: 'grey',
fontSize: 15
},
text: errorMessage,
left: 'center',
top: 'center',
},
};
}
onChartInit(ec) {
this.chartInstance = ec;
}

View file

@ -1,5 +1,5 @@
<div *browserOnly echarts class="echarts" (chartInit)="onChartReady($event)" (chartRendered)="rendered()" [initOpts]="mempoolVsizeFeesInitOptions"
[options]="mempoolVsizeFeesOptions" [style]="{opacity: isLoading ? 0.5 : 1}"></div>
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || isLoading">
<div class="text-center loadingGraphs" *ngIf="!stateService.isBrowser || (isLoading && !error)">
<div class="spinner-border text-light"></div>
</div>

View file

@ -25,7 +25,7 @@ import { download, formatterXAxis, formatterXAxisLabel } from '@app/shared/graph
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MempoolGraphComponent implements OnInit, OnChanges {
@Input() data: any[];
@Input() data: OptimizedMempoolStats[];
@Input() filterSize = 100000;
@Input() limitFilterFee = 1;
@Input() hideCount: boolean = true;
@ -37,6 +37,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
@Input() showZoom = true;
@Input() windowPreferenceOverride: string;
@Input() isLoading: boolean;
@Input() error: any;
mempoolVsizeFeesData: any;
mempoolVsizeFeesOptions: EChartsOption;
@ -72,7 +73,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
}
ngOnChanges(changes) {
if (!this.data) {
if (this.isLoading) {
return;
}
if (!this.data || (this.data && this.data.length === 0) || this.error) {
this.mountMessageOnChart();
return;
}
this.isWidget = this.template === 'widget';
@ -455,6 +460,25 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
};
}
mountMessageOnChart() {
let errorMessage = $localize`:error.general-loading-data:Error loading data.`;
if (!this.error && this.data && this.data.length === 0) { // Empty array
errorMessage = $localize`No data to display yet. Try again later.`;
}
this.mempoolVsizeFeesOptions = {
title: {
textStyle: {
color: 'grey',
fontSize: 15
},
text: errorMessage,
left: 'center',
top: 'center',
},
};
}
getTotalValues = (values: any) => {
let totalValueTemp = 0;
const totalValueArray = [];

View file

@ -5,7 +5,7 @@
<div class="card-header">
<div class="d-flex d-md-block align-items-baseline">
<span i18n="statistics.memory-by-vBytes">Mempool by vBytes (sat/vByte)</span>
<button class="btn p-0 ps-2" style="margin: 0 0 4px 0px" (click)="onSaveChart('mempool')">
<button class="btn p-0 ps-2" style="margin: 0 0 4px 0px" [disabled]="statsError || !mempoolStats?.length" (click)="onSaveChart('mempool')">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
</div>
@ -83,7 +83,7 @@
<div class="incoming-transactions-graph">
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [hideCount]="!showCount"
[limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="showCount ? 50 : 10"
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null" [isLoading]="isLoading"></app-mempool-graph>
[data]="mempoolStats" [isLoading]="isLoading" [error]="statsError"></app-mempool-graph>
</div>
</div>
</div>
@ -95,7 +95,7 @@
<div class="vbytes-title">
<div>
<span i18n="statistics.transaction-vbytes-per-second">Transaction vBytes per second (vB/s)</span>
<button class="btn p-0 ps-2" style="margin: 0 0 4px 0px" (click)="onSaveChart('incoming')">
<button class="btn p-0 ps-2" style="margin: 0 0 4px 0px" [disabled]="statsError || !mempoolTransactionsWeightPerSecondData?.series[0]?.length" (click)="onSaveChart('incoming')">
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
</button>
</div>
@ -111,7 +111,7 @@
<div class="card-body">
<div class="incoming-transactions-graph">
<app-incoming-transactions-graph #incominggraph [height]="500" [left]="65" [template]="'advanced'"
[data]="mempoolTransactionsWeightPerSecondData" [outlierCappingEnabled]="outlierCappingEnabled" [isLoading]="isLoading"></app-incoming-transactions-graph>
[data]="mempoolTransactionsWeightPerSecondData" [outlierCappingEnabled]="outlierCappingEnabled" [isLoading]="isLoading" [error]="statsError"></app-incoming-transactions-graph>
</div>
</div>
</div>

View file

@ -1,8 +1,8 @@
import { Component, OnInit, LOCALE_ID, Inject, ViewChild, ElementRef } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { UntypedFormGroup, UntypedFormBuilder } from '@angular/forms';
import { of, merge} from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { of, merge, Observable} from 'rxjs';
import { catchError, retry, scan, switchMap, tap } from 'rxjs/operators';
import { OptimizedMempoolStats } from '@interfaces/node-api.interface';
import { WebsocketService } from '@app/services/websocket.service';
@ -36,7 +36,7 @@ export class StatisticsComponent implements OnInit {
maxFeeIndex: number;
dropDownOpen = false;
outlierCappingEnabled = false;
mempoolStats: OptimizedMempoolStats[] = [];
mempoolStats: OptimizedMempoolStats[];
mempoolVsizeFeesData: any;
mempoolUnconfirmedTransactionsData: any;
@ -48,6 +48,7 @@ export class StatisticsComponent implements OnInit {
feeLevelDropdownData = [];
timespan = '';
titleCount = $localize`Count`;
statsError: any;
constructor(
@Inject(LOCALE_ID) private locale: string,
@ -88,58 +89,75 @@ export class StatisticsComponent implements OnInit {
this.radioGroupForm.controls.dateSpan.valueChanges
)
.pipe(
tap(() => {
this.statsError = null;
this.isLoading = true;
}),
switchMap(() => {
this.timespan = this.radioGroupForm.controls.dateSpan.value;
this.isLoading = true;
if (this.radioGroupForm.controls.dateSpan.value === '2h') {
this.websocketService.want(['blocks', 'live-2h-chart']);
return this.apiService.list2HStatistics$();
return this.errorHandlerPipe(this.apiService.list2HStatistics$());
}
this.websocketService.want(['blocks']);
if (this.radioGroupForm.controls.dateSpan.value === '24h') {
return this.apiService.list24HStatistics$();
return this.errorHandlerPipe(this.apiService.list24HStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '1w') {
return this.apiService.list1WStatistics$();
return this.errorHandlerPipe(this.apiService.list1WStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '1m') {
return this.apiService.list1MStatistics$();
return this.errorHandlerPipe(this.apiService.list1MStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '3m') {
return this.apiService.list3MStatistics$();
return this.errorHandlerPipe(this.apiService.list3MStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '6m') {
return this.apiService.list6MStatistics$();
return this.errorHandlerPipe(this.apiService.list6MStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '1y') {
return this.apiService.list1YStatistics$();
return this.errorHandlerPipe(this.apiService.list1YStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '2y') {
return this.apiService.list2YStatistics$();
return this.errorHandlerPipe(this.apiService.list2YStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '3y') {
return this.apiService.list3YStatistics$();
return this.errorHandlerPipe(this.apiService.list3YStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === '4y') {
return this.apiService.list4YStatistics$();
return this.errorHandlerPipe(this.apiService.list4YStatistics$());
}
if (this.radioGroupForm.controls.dateSpan.value === 'all') {
return this.apiService.listAllTimeStatistics$();
return this.errorHandlerPipe(this.apiService.listAllTimeStatistics$());
}
})
}),
switchMap((mempoolStats) => {
if (this.radioGroupForm.controls.dateSpan.value === '2h') {
return merge(
this.stateService.live2Chart$.pipe(
scan((acc, stats) => {
const now = Date.now() / 1000;
const start = now - (2 * 60 * 60);
this.statsError = null;
acc.unshift(stats);
acc = acc.filter(p => p.added >= start);
return acc;
}, (mempoolStats || []))
),
of(mempoolStats)
);
} else {
return of(mempoolStats);
}
}),
)
.subscribe((mempoolStats: any) => {
this.mempoolStats = mempoolStats;
this.handleNewMempoolData(this.mempoolStats.concat([]));
if (this.mempoolStats) {
this.handleNewMempoolData(this.mempoolStats.concat([]));
}
this.isLoading = false;
});
this.stateService.live2Chart$
.subscribe((mempoolStats) => {
this.mempoolStats.unshift(mempoolStats);
this.mempoolStats = this.mempoolStats.slice(0, this.mempoolStats.length - 1);
this.handleNewMempoolData(this.mempoolStats.concat([]));
});
}
handleNewMempoolData(mempoolStats: OptimizedMempoolStats[]) {
@ -171,6 +189,16 @@ export class StatisticsComponent implements OnInit {
document.location.reload();
}
errorHandlerPipe(obs: Observable<OptimizedMempoolStats[]>) {
return obs.pipe(
retry({ count: 3, delay: 1000 }),
catchError((err) => {
this.statsError = err;
return of(null);
}),
);
}
setFeeLevelDropdownData() {
let _feeLevels = feeLevels;
let _chartColors = chartColors;

View file

@ -48,13 +48,15 @@
<div class="card-body">
<ng-container *ngTemplateOutlet="mempoolTable; context: { $implicit: mempoolInfoData }"></ng-container>
<h5 class="card-title mt-3" i18n="dashboard.incoming-transactions">Incoming Transactions</h5>
<div class="mempool-graph" *ngIf="(mempoolStats$ | async) as mempoolStats">
<div class="mempool-graph">
<app-incoming-transactions-graph
[height]="incomingGraphHeight"
[left]="50"
[right]="20"
[data]="mempoolStats?.weightPerSecond"
[windowPreferenceOverride]="'2h'"
[isLoading]="isMempoolStatsLoading"
[error]="mempoolStatsError"
></app-incoming-transactions-graph>
</div>
</div>

View file

@ -1,6 +1,6 @@
import { AfterViewInit, ChangeDetectionStrategy, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Inject, OnDestroy, OnInit, PLATFORM_ID } from '@angular/core';
import { combineLatest, EMPTY, fromEvent, interval, merge, Observable, of, Subject, Subscription, timer } from 'rxjs';
import { catchError, delayWhen, distinctUntilChanged, filter, map, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
import { catchError, delayWhen, distinctUntilChanged, filter, map, retry, scan, share, shareReplay, startWith, switchMap, takeUntil, tap, throttleTime } from 'rxjs/operators';
import { AuditStatus, BlockExtended, CurrentPegs, FederationAddress, FederationUtxo, OptimizedMempoolStats, RecentPeg } from '@interfaces/node-api.interface';
import { MempoolInfo, ReplacementInfo } from '@interfaces/websocket.interface';
import { ApiService } from '@app/services/api.service';
@ -44,7 +44,9 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
replacements$: Observable<ReplacementInfo[]>;
latestBlockHeight: number;
mempoolTransactionsWeightPerSecondData: any;
mempoolStats$: Observable<MempoolStatsData>;
mempoolStats: MempoolStatsData;
mempoolStatsError: any;
isMempoolStatsLoading = true;
transactionsWeightPerSecondOptions: any;
isLoadingWebSocket$: Observable<boolean>;
liquidPegsMonth$: Observable<any>;
@ -59,6 +61,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
emergencySpentUtxosStats$: Observable<any>;
fullHistory$: Observable<any>;
isLoad: boolean = true;
mempoolStatsSubscription: Subscription;
filterSubscription: Subscription;
mempoolInfoSubscription: Subscription;
currencySubscription: Subscription;
@ -90,6 +93,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
private websocketService: WebsocketService,
private seoService: SeoService,
@Inject(PLATFORM_ID) private platformId: object,
private cd: ChangeDetectorRef,
) {
this.webGlEnabled = this.stateService.isBrowser && detectWebGL();
}
@ -99,6 +103,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngOnDestroy(): void {
this.mempoolStatsSubscription.unsubscribe();
this.filterSubscription.unsubscribe();
this.mempoolInfoSubscription.unsubscribe();
this.currencySubscription.unsubscribe();
@ -213,13 +218,19 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
this.replacements$ = this.stateService.rbfLatestSummary$;
this.mempoolStats$ = this.stateService.connectionState$
this.mempoolStatsSubscription = this.stateService.connectionState$
.pipe(
filter((state) => state === 2),
tap(() => {
this.isMempoolStatsLoading = true;
this.mempoolStatsError = null;
}),
switchMap(() => this.apiService.list2HStatistics$().pipe(
catchError((e) => {
retry({ count: 3, delay: 1000 }),
catchError((err) => {
this.mempoolStatsError = err;
return of(null);
})
}),
)),
switchMap((mempoolStats) => {
return merge(
@ -228,6 +239,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
scan((acc, stats) => {
const now = Date.now() / 1000;
const start = now - (2 * 60 * 60);
this.mempoolStatsError = null;
acc.unshift(stats);
acc = acc.filter(p => p.added >= start);
return acc;
@ -246,8 +258,11 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
return null;
}
}),
shareReplay(1),
);
).subscribe((mempoolStats) => {
this.isMempoolStatsLoading = false;
this.mempoolStats = mempoolStats;
this.cd.markForCheck();
});
if (this.stateService.network === 'liquid') {
this.auditStatus$ = this.stateService.blocks$.pipe(