+
\ No newline at end of file
diff --git a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
index dfec9f355..aceb5b99f 100644
--- a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
+++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts
@@ -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;
}
diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.html b/frontend/src/app/components/mempool-graph/mempool-graph.component.html
index 155d73ecf..7d6c9eeea 100644
--- a/frontend/src/app/components/mempool-graph/mempool-graph.component.html
+++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.html
@@ -1,5 +1,5 @@
-
+
\ No newline at end of file
diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
index 1409ea8bb..583f07779 100644
--- a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
+++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts
@@ -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 = [];
diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html
index acd7a64f1..98116df2d 100644
--- a/frontend/src/app/components/statistics/statistics.component.html
+++ b/frontend/src/app/components/statistics/statistics.component.html
@@ -5,7 +5,7 @@
@@ -95,7 +95,7 @@
Transaction vBytes per second (vB/s)
-
@@ -111,7 +111,7 @@
+ [data]="mempoolTransactionsWeightPerSecondData" [outlierCappingEnabled]="outlierCappingEnabled" [isLoading]="isLoading" [error]="statsError">
diff --git a/frontend/src/app/components/statistics/statistics.component.ts b/frontend/src/app/components/statistics/statistics.component.ts
index af486970e..87601dee7 100644
--- a/frontend/src/app/components/statistics/statistics.component.ts
+++ b/frontend/src/app/components/statistics/statistics.component.ts
@@ -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
) {
+ return obs.pipe(
+ retry({ count: 3, delay: 1000 }),
+ catchError((err) => {
+ this.statsError = err;
+ return of(null);
+ }),
+ );
+ }
+
setFeeLevelDropdownData() {
let _feeLevels = feeLevels;
let _chartColors = chartColors;
diff --git a/frontend/src/app/dashboard/dashboard.component.html b/frontend/src/app/dashboard/dashboard.component.html
index 90454d9a3..e485bcf9b 100644
--- a/frontend/src/app/dashboard/dashboard.component.html
+++ b/frontend/src/app/dashboard/dashboard.component.html
@@ -48,13 +48,15 @@
Incoming Transactions
-
diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts
index 61a2ce3f0..bad66ba90 100644
--- a/frontend/src/app/dashboard/dashboard.component.ts
+++ b/frontend/src/app/dashboard/dashboard.component.ts
@@ -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
;
latestBlockHeight: number;
mempoolTransactionsWeightPerSecondData: any;
- mempoolStats$: Observable;
+ mempoolStats: MempoolStatsData;
+ mempoolStatsError: any;
+ isMempoolStatsLoading = true;
transactionsWeightPerSecondOptions: any;
isLoadingWebSocket$: Observable;
liquidPegsMonth$: Observable;
@@ -59,6 +61,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit {
emergencySpentUtxosStats$: Observable;
fullHistory$: Observable;
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(